Branch data Line data Source code
1 : : /*
2 : :
3 : : Perf trampoline instrumentation
4 : : ===============================
5 : :
6 : : This file contains instrumentation to allow to associate
7 : : calls to the CPython eval loop back to the names of the Python
8 : : functions and filename being executed.
9 : :
10 : : Many native performance profilers like the Linux perf tools are
11 : : only available to 'see' the C stack when sampling from the profiled
12 : : process. This means that if we have the following python code:
13 : :
14 : : import time
15 : : def foo(n):
16 : : # Some CPU intensive code
17 : :
18 : : def bar(n):
19 : : foo(n)
20 : :
21 : : def baz(n):
22 : : bar(n)
23 : :
24 : : baz(10000000)
25 : :
26 : : A performance profiler that is only able to see native frames will
27 : : produce the following backtrace when sampling from foo():
28 : :
29 : : _PyEval_EvalFrameDefault -----> Evaluation frame of foo()
30 : : _PyEval_Vector
31 : : _PyFunction_Vectorcall
32 : : PyObject_Vectorcall
33 : : call_function
34 : :
35 : : _PyEval_EvalFrameDefault ------> Evaluation frame of bar()
36 : : _PyEval_EvalFrame
37 : : _PyEval_Vector
38 : : _PyFunction_Vectorcall
39 : : PyObject_Vectorcall
40 : : call_function
41 : :
42 : : _PyEval_EvalFrameDefault -------> Evaluation frame of baz()
43 : : _PyEval_EvalFrame
44 : : _PyEval_Vector
45 : : _PyFunction_Vectorcall
46 : : PyObject_Vectorcall
47 : : call_function
48 : :
49 : : ...
50 : :
51 : : Py_RunMain
52 : :
53 : : Because the profiler is only able to see the native frames and the native
54 : : function that runs the evaluation loop is the same (_PyEval_EvalFrameDefault)
55 : : then the profiler and any reporter generated by it will not be able to
56 : : associate the names of the Python functions and the filenames associated with
57 : : those calls, rendering the results useless in the Python world.
58 : :
59 : : To fix this problem, we introduce the concept of a trampoline frame. A
60 : : trampoline frame is a piece of code that is unique per Python code object that
61 : : is executed before entering the CPython eval loop. This piece of code just
62 : : calls the original Python evaluation function (_PyEval_EvalFrameDefault) and
63 : : forwards all the arguments received. In this way, when a profiler samples
64 : : frames from the previous example it will see;
65 : :
66 : : _PyEval_EvalFrameDefault -----> Evaluation frame of foo()
67 : : [Jit compiled code 3]
68 : : _PyEval_Vector
69 : : _PyFunction_Vectorcall
70 : : PyObject_Vectorcall
71 : : call_function
72 : :
73 : : _PyEval_EvalFrameDefault ------> Evaluation frame of bar()
74 : : [Jit compiled code 2]
75 : : _PyEval_EvalFrame
76 : : _PyEval_Vector
77 : : _PyFunction_Vectorcall
78 : : PyObject_Vectorcall
79 : : call_function
80 : :
81 : : _PyEval_EvalFrameDefault -------> Evaluation frame of baz()
82 : : [Jit compiled code 1]
83 : : _PyEval_EvalFrame
84 : : _PyEval_Vector
85 : : _PyFunction_Vectorcall
86 : : PyObject_Vectorcall
87 : : call_function
88 : :
89 : : ...
90 : :
91 : : Py_RunMain
92 : :
93 : : When we generate every unique copy of the trampoline (what here we called "[Jit
94 : : compiled code N]") we write the relationship between the compiled code and the
95 : : Python function that is associated with it. Every profiler requires this
96 : : information in a different format. For example, the Linux "perf" profiler
97 : : requires a file in "/tmp/perf-PID.map" (name and location not configurable)
98 : : with the following format:
99 : :
100 : : <compiled code address> <compiled code size> <name of the compiled code>
101 : :
102 : : If this file is available when "perf" generates reports, it will automatically
103 : : associate every trampoline with the Python function that it is associated with
104 : : allowing it to generate reports that include Python information. These reports
105 : : then can also be filtered in a way that *only* Python information appears.
106 : :
107 : : Notice that for this to work, there must be a unique copied of the trampoline
108 : : per Python code object even if the code in the trampoline is the same. To
109 : : achieve this we have a assembly template in Objects/asm_trampiline.S that is
110 : : compiled into the Python executable/shared library. This template generates a
111 : : symbol that maps the start of the assembly code and another that marks the end
112 : : of the assembly code for the trampoline. Then, every time we need a unique
113 : : trampoline for a Python code object, we copy the assembly code into a mmaped
114 : : area that has executable permissions and we return the start of that area as
115 : : our trampoline function.
116 : :
117 : : Asking for a mmap-ed memory area for trampoline is very wasteful so we
118 : : allocate big arenas of memory in a single mmap call, we populate the entire
119 : : arena with copies of the trampoline (this allows us to now have to invalidate
120 : : the icache for the instructions in the page) and then we return the next
121 : : available chunk every time someone asks for a new trampoline. We keep a linked
122 : : list of arenas in case the current memory arena is exhausted and another one is
123 : : needed.
124 : :
125 : : For the best results, Python should be compiled with
126 : : CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" as this allows
127 : : profilers to unwind using only the frame pointer and not on DWARF debug
128 : : information (note that as trampilines are dynamically generated there won't be
129 : : any DWARF information available for them).
130 : : */
131 : :
132 : : #include "Python.h"
133 : : #include "pycore_ceval.h"
134 : : #include "pycore_frame.h"
135 : : #include "pycore_interp.h"
136 : :
137 : :
138 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
139 : :
140 : : #include <fcntl.h>
141 : : #include <stdio.h>
142 : : #include <stdlib.h>
143 : : #include <sys/mman.h>
144 : : #include <sys/types.h>
145 : : #include <unistd.h>
146 : :
147 : : #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
148 : : #define PY_HAVE_INVALIDATE_ICACHE
149 : :
150 : : #if defined(__clang__) || defined(__GNUC__)
151 : : extern void __clear_cache(void *, void*);
152 : : #endif
153 : :
154 : : static void invalidate_icache(char* begin, char*end) {
155 : : #if defined(__clang__) || defined(__GNUC__)
156 : : return __clear_cache(begin, end);
157 : : #else
158 : : return;
159 : : #endif
160 : : }
161 : : #endif
162 : :
163 : : /* The function pointer is passed as last argument. The other three arguments
164 : : * are passed in the same order as the function requires. This results in
165 : : * shorter, more efficient ASM code for trampoline.
166 : : */
167 : : typedef PyObject *(*py_evaluator)(PyThreadState *, _PyInterpreterFrame *,
168 : : int throwflag);
169 : : typedef PyObject *(*py_trampoline)(PyThreadState *, _PyInterpreterFrame *, int,
170 : : py_evaluator);
171 : :
172 : : extern void *_Py_trampoline_func_start; // Start of the template of the
173 : : // assembly trampoline
174 : : extern void *
175 : : _Py_trampoline_func_end; // End of the template of the assembly trampoline
176 : :
177 : : struct code_arena_st {
178 : : char *start_addr; // Start of the memory arena
179 : : char *current_addr; // Address of the current trampoline within the arena
180 : : size_t size; // Size of the memory arena
181 : : size_t size_left; // Remaining size of the memory arena
182 : : size_t code_size; // Size of the code of every trampoline in the arena
183 : : struct code_arena_st
184 : : *prev; // Pointer to the arena or NULL if this is the first arena.
185 : : };
186 : :
187 : : typedef struct code_arena_st code_arena_t;
188 : : typedef struct trampoline_api_st trampoline_api_t;
189 : :
190 : : #define perf_status _PyRuntime.ceval.perf.status
191 : : #define extra_code_index _PyRuntime.ceval.perf.extra_code_index
192 : : #define perf_code_arena _PyRuntime.ceval.perf.code_arena
193 : : #define trampoline_api _PyRuntime.ceval.perf.trampoline_api
194 : : #define perf_map_file _PyRuntime.ceval.perf.map_file
195 : :
196 : : static void *
197 : 0 : perf_map_get_file(void)
198 : : {
199 [ # # ]: 0 : if (perf_map_file) {
200 : 0 : return perf_map_file;
201 : : }
202 : : char filename[100];
203 : 0 : pid_t pid = getpid();
204 : : // Location and file name of perf map is hard-coded in perf tool.
205 : : // Use exclusive create flag wit nofollow to prevent symlink attacks.
206 : 0 : int flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC;
207 : 0 : snprintf(filename, sizeof(filename) - 1, "/tmp/perf-%jd.map",
208 : : (intmax_t)pid);
209 : 0 : int fd = open(filename, flags, 0600);
210 [ # # ]: 0 : if (fd == -1) {
211 : 0 : perf_status = PERF_STATUS_FAILED;
212 : 0 : PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
213 : 0 : return NULL;
214 : : }
215 : 0 : perf_map_file = fdopen(fd, "w");
216 [ # # ]: 0 : if (!perf_map_file) {
217 : 0 : perf_status = PERF_STATUS_FAILED;
218 : 0 : PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
219 : 0 : close(fd);
220 : 0 : return NULL;
221 : : }
222 : 0 : return perf_map_file;
223 : : }
224 : :
225 : : static int
226 : 0 : perf_map_close(void *state)
227 : : {
228 : 0 : FILE *fp = (FILE *)state;
229 : 0 : int ret = 0;
230 [ # # ]: 0 : if (fp) {
231 : 0 : ret = fclose(fp);
232 : : }
233 : 0 : perf_map_file = NULL;
234 : 0 : perf_status = PERF_STATUS_NO_INIT;
235 : 0 : return ret;
236 : : }
237 : :
238 : : static void
239 : 0 : perf_map_write_entry(void *state, const void *code_addr,
240 : : unsigned int code_size, PyCodeObject *co)
241 : : {
242 : : assert(state != NULL);
243 : 0 : FILE *method_file = (FILE *)state;
244 : 0 : const char *entry = PyUnicode_AsUTF8(co->co_qualname);
245 [ # # ]: 0 : if (entry == NULL) {
246 : 0 : _PyErr_WriteUnraisableMsg("Failed to get qualname from code object",
247 : : NULL);
248 : 0 : return;
249 : : }
250 : 0 : const char *filename = PyUnicode_AsUTF8(co->co_filename);
251 [ # # ]: 0 : if (filename == NULL) {
252 : 0 : _PyErr_WriteUnraisableMsg("Failed to get filename from code object",
253 : : NULL);
254 : 0 : return;
255 : : }
256 : 0 : fprintf(method_file, "%p %x py::%s:%s\n", code_addr, code_size, entry,
257 : : filename);
258 : 0 : fflush(method_file);
259 : : }
260 : :
261 : : _PyPerf_Callbacks _Py_perfmap_callbacks = {
262 : : &perf_map_get_file,
263 : : &perf_map_write_entry,
264 : : &perf_map_close
265 : : };
266 : :
267 : : static int
268 : 0 : new_code_arena(void)
269 : : {
270 : : // non-trivial programs typically need 64 to 256 kiB.
271 : 0 : size_t mem_size = 4096 * 16;
272 : : assert(mem_size % sysconf(_SC_PAGESIZE) == 0);
273 : : char *memory =
274 : 0 : mmap(NULL, // address
275 : : mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
276 : : -1, // fd (not used here)
277 : : 0); // offset (not used here)
278 [ # # ]: 0 : if (!memory) {
279 : 0 : PyErr_SetFromErrno(PyExc_OSError);
280 : 0 : _PyErr_WriteUnraisableMsg(
281 : : "Failed to create new mmap for perf trampoline", NULL);
282 : 0 : perf_status = PERF_STATUS_FAILED;
283 : 0 : return -1;
284 : : }
285 : 0 : void *start = &_Py_trampoline_func_start;
286 : 0 : void *end = &_Py_trampoline_func_end;
287 : 0 : size_t code_size = end - start;
288 : : // TODO: Check the effect of alignment of the code chunks. Initial investigation
289 : : // showed that this has no effect on performance in x86-64 or aarch64 and the current
290 : : // version has the advantage that the unwinder in GDB can unwind across JIT-ed code.
291 : : //
292 : : // We should check the values in the future and see if there is a
293 : : // measurable performance improvement by rounding trampolines up to 32-bit
294 : : // or 64-bit alignment.
295 : :
296 : 0 : size_t n_copies = mem_size / code_size;
297 [ # # ]: 0 : for (size_t i = 0; i < n_copies; i++) {
298 : 0 : memcpy(memory + i * code_size, start, code_size * sizeof(char));
299 : : }
300 : : // Some systems may prevent us from creating executable code on the fly.
301 : 0 : int res = mprotect(memory, mem_size, PROT_READ | PROT_EXEC);
302 [ # # ]: 0 : if (res == -1) {
303 : 0 : PyErr_SetFromErrno(PyExc_OSError);
304 : 0 : munmap(memory, mem_size);
305 : 0 : _PyErr_WriteUnraisableMsg(
306 : : "Failed to set mmap for perf trampoline to PROT_READ | PROT_EXEC",
307 : : NULL);
308 : 0 : return -1;
309 : : }
310 : :
311 : : #ifdef PY_HAVE_INVALIDATE_ICACHE
312 : : // Before the JIT can run a block of code that has been emitted it must invalidate
313 : : // the instruction cache on some platforms like arm and aarch64.
314 : : invalidate_icache(memory, memory + mem_size);
315 : : #endif
316 : :
317 : 0 : code_arena_t *new_arena = PyMem_RawCalloc(1, sizeof(code_arena_t));
318 [ # # ]: 0 : if (new_arena == NULL) {
319 : 0 : PyErr_NoMemory();
320 : 0 : munmap(memory, mem_size);
321 : 0 : _PyErr_WriteUnraisableMsg("Failed to allocate new code arena struct",
322 : : NULL);
323 : 0 : return -1;
324 : : }
325 : :
326 : 0 : new_arena->start_addr = memory;
327 : 0 : new_arena->current_addr = memory;
328 : 0 : new_arena->size = mem_size;
329 : 0 : new_arena->size_left = mem_size;
330 : 0 : new_arena->code_size = code_size;
331 : 0 : new_arena->prev = perf_code_arena;
332 : 0 : perf_code_arena = new_arena;
333 : 0 : return 0;
334 : : }
335 : :
336 : : static void
337 : 25 : free_code_arenas(void)
338 : : {
339 : 25 : code_arena_t *cur = perf_code_arena;
340 : : code_arena_t *prev;
341 : 25 : perf_code_arena = NULL; // invalid static pointer
342 [ - + ]: 25 : while (cur) {
343 : 0 : munmap(cur->start_addr, cur->size);
344 : 0 : prev = cur->prev;
345 : 0 : PyMem_RawFree(cur);
346 : 0 : cur = prev;
347 : : }
348 : 25 : }
349 : :
350 : : static inline py_trampoline
351 : 0 : code_arena_new_code(code_arena_t *code_arena)
352 : : {
353 : 0 : py_trampoline trampoline = (py_trampoline)code_arena->current_addr;
354 : 0 : code_arena->size_left -= code_arena->code_size;
355 : 0 : code_arena->current_addr += code_arena->code_size;
356 : 0 : return trampoline;
357 : : }
358 : :
359 : : static inline py_trampoline
360 : 0 : compile_trampoline(void)
361 : : {
362 [ # # ]: 0 : if ((perf_code_arena == NULL) ||
363 [ # # ]: 0 : (perf_code_arena->size_left <= perf_code_arena->code_size)) {
364 [ # # ]: 0 : if (new_code_arena() < 0) {
365 : 0 : return NULL;
366 : : }
367 : : }
368 : : assert(perf_code_arena->size_left <= perf_code_arena->size);
369 : 0 : return code_arena_new_code(perf_code_arena);
370 : : }
371 : :
372 : : static PyObject *
373 : 0 : py_trampoline_evaluator(PyThreadState *ts, _PyInterpreterFrame *frame,
374 : : int throw)
375 : : {
376 [ # # ]: 0 : if (perf_status == PERF_STATUS_FAILED ||
377 [ # # ]: 0 : perf_status == PERF_STATUS_NO_INIT) {
378 : 0 : goto default_eval;
379 : : }
380 : 0 : PyCodeObject *co = frame->f_code;
381 : 0 : py_trampoline f = NULL;
382 : : assert(extra_code_index != -1);
383 : 0 : int ret = _PyCode_GetExtra((PyObject *)co, extra_code_index, (void **)&f);
384 [ # # # # ]: 0 : if (ret != 0 || f == NULL) {
385 : : // This is the first time we see this code object so we need
386 : : // to compile a trampoline for it.
387 : 0 : py_trampoline new_trampoline = compile_trampoline();
388 [ # # ]: 0 : if (new_trampoline == NULL) {
389 : 0 : goto default_eval;
390 : : }
391 : 0 : trampoline_api.write_state(trampoline_api.state, new_trampoline,
392 : 0 : perf_code_arena->code_size, co);
393 : 0 : _PyCode_SetExtra((PyObject *)co, extra_code_index,
394 : : (void *)new_trampoline);
395 : 0 : f = new_trampoline;
396 : : }
397 : : assert(f != NULL);
398 : 0 : return f(ts, frame, throw, _PyEval_EvalFrameDefault);
399 : 0 : default_eval:
400 : : // Something failed, fall back to the default evaluator.
401 : 0 : return _PyEval_EvalFrameDefault(ts, frame, throw);
402 : : }
403 : : #endif // PY_HAVE_PERF_TRAMPOLINE
404 : :
405 : : int
406 : 0 : _PyIsPerfTrampolineActive(void)
407 : : {
408 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
409 : 0 : PyThreadState *tstate = _PyThreadState_GET();
410 : 0 : return tstate->interp->eval_frame == py_trampoline_evaluator;
411 : : #endif
412 : : return 0;
413 : : }
414 : :
415 : : void
416 : 0 : _PyPerfTrampoline_GetCallbacks(_PyPerf_Callbacks *callbacks)
417 : : {
418 [ # # ]: 0 : if (callbacks == NULL) {
419 : 0 : return;
420 : : }
421 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
422 : 0 : callbacks->init_state = trampoline_api.init_state;
423 : 0 : callbacks->write_state = trampoline_api.write_state;
424 : 0 : callbacks->free_state = trampoline_api.free_state;
425 : : #endif
426 : 0 : return;
427 : : }
428 : :
429 : : int
430 : 0 : _PyPerfTrampoline_SetCallbacks(_PyPerf_Callbacks *callbacks)
431 : : {
432 [ # # ]: 0 : if (callbacks == NULL) {
433 : 0 : return -1;
434 : : }
435 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
436 [ # # ]: 0 : if (trampoline_api.state) {
437 : 0 : _PyPerfTrampoline_Fini();
438 : : }
439 : 0 : trampoline_api.init_state = callbacks->init_state;
440 : 0 : trampoline_api.write_state = callbacks->write_state;
441 : 0 : trampoline_api.free_state = callbacks->free_state;
442 : 0 : trampoline_api.state = NULL;
443 : 0 : perf_status = PERF_STATUS_OK;
444 : : #endif
445 : 0 : return 0;
446 : : }
447 : :
448 : : int
449 : 0 : _PyPerfTrampoline_Init(int activate)
450 : : {
451 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
452 : 0 : PyThreadState *tstate = _PyThreadState_GET();
453 [ # # ]: 0 : if (tstate->interp->eval_frame &&
454 [ # # ]: 0 : tstate->interp->eval_frame != py_trampoline_evaluator) {
455 : 0 : PyErr_SetString(PyExc_RuntimeError,
456 : : "Trampoline cannot be initialized as a custom eval "
457 : : "frame is already present");
458 : 0 : return -1;
459 : : }
460 [ # # ]: 0 : if (!activate) {
461 : 0 : tstate->interp->eval_frame = NULL;
462 : : }
463 : : else {
464 : 0 : tstate->interp->eval_frame = py_trampoline_evaluator;
465 [ # # ]: 0 : if (new_code_arena() < 0) {
466 : 0 : return -1;
467 : : }
468 [ # # ]: 0 : if (trampoline_api.state == NULL) {
469 : 0 : void *state = trampoline_api.init_state();
470 [ # # ]: 0 : if (state == NULL) {
471 : 0 : return -1;
472 : : }
473 : 0 : trampoline_api.state = state;
474 : : }
475 : 0 : extra_code_index = _PyEval_RequestCodeExtraIndex(NULL);
476 [ # # ]: 0 : if (extra_code_index == -1) {
477 : 0 : return -1;
478 : : }
479 : 0 : perf_status = PERF_STATUS_OK;
480 : : }
481 : : #endif
482 : 0 : return 0;
483 : : }
484 : :
485 : : int
486 : 25 : _PyPerfTrampoline_Fini(void)
487 : : {
488 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
489 : 25 : PyThreadState *tstate = _PyThreadState_GET();
490 [ - + ]: 25 : if (tstate->interp->eval_frame == py_trampoline_evaluator) {
491 : 0 : tstate->interp->eval_frame = NULL;
492 : : }
493 : 25 : free_code_arenas();
494 [ - + ]: 25 : if (trampoline_api.state != NULL) {
495 : 0 : trampoline_api.free_state(trampoline_api.state);
496 : 0 : trampoline_api.state = NULL;
497 : : }
498 : 25 : extra_code_index = -1;
499 : : #endif
500 : 25 : return 0;
501 : : }
502 : :
503 : : PyStatus
504 : 0 : _PyPerfTrampoline_AfterFork_Child(void)
505 : : {
506 : : #ifdef PY_HAVE_PERF_TRAMPOLINE
507 : : // Restart trampoline in file in child.
508 : 0 : int was_active = _PyIsPerfTrampolineActive();
509 : 0 : _PyPerfTrampoline_Fini();
510 [ # # ]: 0 : if (was_active) {
511 : 0 : _PyPerfTrampoline_Init(1);
512 : : }
513 : : #endif
514 : 0 : return PyStatus_Ok();
515 : : }
|