Skip to content

Commit

Permalink
fix(linux): improved container support
Browse files Browse the repository at this point in the history
We make use of the /proc/root of the procfs to improve the chances
that we can infer the required information, such as the location
of the runtime structure and the interpreter version from binaries
pointed at by the actual process root. This is relevant for
processes running inside containers, since their FS root does not
coincide with the host root.
  • Loading branch information
P403n1x87 committed Mar 31, 2024
1 parent 5303860 commit cd66c47
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 24 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
2024-xx-xx v3.6.1

Improve support for Python processes running in containers.

Bugfix: fixed a bug with the MOJO binary format that caused the line end
position to wrongly be set to a non-zero value for CPython < 3.11, where line
end information is not actually available.
Expand Down
23 changes: 23 additions & 0 deletions src/linux/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ptrace.h>

#include "../error.h"
#include "../hints.h"
#include "../stats.h"


Expand Down Expand Up @@ -109,3 +111,24 @@ _procfs(pid_t pid, char * file) {

return fp;
}


// ----------------------------------------------------------------------------
static inline char *
proc_root(pid_t pid, char * file) {
if (file[0] != '/') {
log_e("File path is not absolute");
return NULL;

Check warning on line 121 in src/linux/common.h

View check run for this annotation

Codecov / codecov/patch

src/linux/common.h#L120-L121

Added lines #L120 - L121 were not covered by tests
}

char * proc_root = calloc(1, strlen(file) + 24);
if (!isvalid(proc_root))
return NULL;

Check warning on line 126 in src/linux/common.h

View check run for this annotation

Codecov / codecov/patch

src/linux/common.h#L126

Added line #L126 was not covered by tests

if (sprintf(proc_root, "/proc/%d/root%s", pid, file) < 0) {
free(proc_root);
return NULL;

Check warning on line 130 in src/linux/common.h

View check run for this annotation

Codecov / codecov/patch

src/linux/common.h#L129-L130

Added lines #L129 - L130 were not covered by tests
}

return proc_root;
}
20 changes: 10 additions & 10 deletions src/linux/py_proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,16 +455,16 @@ _py_proc__parse_maps_file(py_proc_t * self) {
// The first memory map of the executable
if (!isvalid(pd->maps[MAP_BIN].path) && strcmp(pd->exe_path, pathname) == 0) {
map = &(pd->maps[MAP_BIN]);
map->path = strndup(pathname, strlen(pathname));
map->path = proc_root(self->pid, pathname);
if (!isvalid(map->path)) {
log_ie("Cannot duplicate path name");
log_e("Cannot get proc root path for %s", pathname);

Check warning on line 460 in src/linux/py_proc.h

View check run for this annotation

Codecov / codecov/patch

src/linux/py_proc.h#L460

Added line #L460 was not covered by tests
set_error(EPROC);
FAIL;
}
map->file_size = _file_size(pathname);
map->file_size = _file_size(map->path);
map->base = (void *) lower;
map->size = upper - lower;
map->has_symbols = success(_py_proc__analyze_elf(self, pathname, (void *) lower));
map->has_symbols = success(_py_proc__analyze_elf(self, map->path, (void *) lower));
if (map->has_symbols) {
map->bss_base = self->map.bss.base;
map->bss_size = self->map.bss.size;
Expand All @@ -479,13 +479,13 @@ _py_proc__parse_maps_file(py_proc_t * self) {
int has_symbols = success(_py_proc__analyze_elf(self, pathname, (void *) lower));
if (has_symbols) {
map = &(pd->maps[MAP_LIBSYM]);
map->path = strndup(pathname, strlen(pathname));
map->path = proc_root(self->pid, pathname);
if (!isvalid(map->path)) {
log_ie("Cannot duplicate path name");
log_e("Cannot get proc root path for %s", pathname);

Check warning on line 484 in src/linux/py_proc.h

View check run for this annotation

Codecov / codecov/patch

src/linux/py_proc.h#L484

Added line #L484 was not covered by tests
set_error(EPROC);
FAIL;
}
map->file_size = _file_size(pathname);
map->file_size = _file_size(map->path);
map->base = (void *) lower;
map->size = upper - lower;
map->has_symbols = TRUE;
Expand All @@ -503,13 +503,13 @@ _py_proc__parse_maps_file(py_proc_t * self) {
unsigned int v;
if (sscanf(needle, "libpython%u.%u", &v, &v) == 2) {
map = &(pd->maps[MAP_LIBNEEDLE]);
map->path = needle_path = strndup(pathname, strlen(pathname));
map->path = needle_path = proc_root(self->pid, pathname);

Check warning on line 506 in src/linux/py_proc.h

View check run for this annotation

Codecov / codecov/patch

src/linux/py_proc.h#L506

Added line #L506 was not covered by tests
if (!isvalid(map->path)) {
log_ie("Cannot duplicate path name");
log_e("Cannot get proc root path for %s", pathname);

Check warning on line 508 in src/linux/py_proc.h

View check run for this annotation

Codecov / codecov/patch

src/linux/py_proc.h#L508

Added line #L508 was not covered by tests
set_error(EPROC);
FAIL;
}
map->file_size = _file_size(pathname);
map->file_size = _file_size(map->path);

Check warning on line 512 in src/linux/py_proc.h

View check run for this annotation

Codecov / codecov/patch

src/linux/py_proc.h#L512

Added line #L512 was not covered by tests
map->base = (void *) lower;
map->size = upper - lower;
map->has_symbols = FALSE;
Expand Down
24 changes: 10 additions & 14 deletions src/py_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,14 @@ _get_version_from_executable(char * binary, int * major, int * minor, int * patc
#endif

fp = _popen(cmd, "r");
if (!isvalid(fp)) {
set_error(EPROC);
if (!isvalid(fp))
FAIL;
}

while (fgets(version, sizeof(version) - 1, fp) != NULL) {
if (sscanf(version, "Python %d.%d.%d", major, minor, patch) == 3)
SUCCESS;
}

set_error(EPROC);
FAIL;
} /* _get_version_from_executable */

Expand All @@ -129,37 +126,34 @@ _get_version_from_filename(char * filename, const char * needle, int * major, in
#if defined PL_LINUX /* LINUX */
char * base = filename;
char * end = base + strlen(base);
size_t needle_len = strlen(needle);

while (base < end) {
base = strstr(base, needle);
if (!isvalid(base)) {
break;
}
if (sscanf(base + strlen(needle), "%u.%u", major, minor) == 2) {
base += needle_len;
if (sscanf(base, "%u.%u", major, minor) == 2) {
SUCCESS;
}
}

#elif defined PL_WIN /* WIN */
// Assume the library path is of the form *.python3[0-9]+[.]dll
int n = strlen(filename);
if (n < 10) {
set_error(EPROC);
if (n < 10)
FAIL;
}

char * p = filename + n - 1;
while (*(p--) != 'n' && p > filename);
p++;
*major = *(p++) - '0';
if (*major != 3) {
set_error(EPROC);
if (*major != 3)
FAIL;
}

if (sscanf(p,"%d.dll", minor) == 1) {
if (sscanf(p,"%d.dll", minor) == 1)
SUCCESS;
}

#elif defined PL_MACOS /* MAC */
char * ver_needle = strstr(filename, "3.");
Expand All @@ -169,7 +163,6 @@ _get_version_from_filename(char * filename, const char * needle, int * major, in

#endif

set_error(EPROC);
FAIL;
} /* _get_version_from_filename */

Expand Down Expand Up @@ -648,6 +641,9 @@ _py_proc__run(py_proc_t * self) {
TIMER_STOP;
}

if (is_fatal(austin_errno))
FAIL;

TIMER_END

log_d("_py_proc__init timer loop terminated");
Expand Down

0 comments on commit cd66c47

Please sign in to comment.