1import os 2import sys 3 4from llvmlite import ir 5 6from numba.core import types, utils, config, cgutils 7from numba import gdb, gdb_init, gdb_breakpoint 8from numba.core.extending import overload, intrinsic 9 10_path = os.path.dirname(__file__) 11 12_platform = sys.platform 13_unix_like = (_platform.startswith('linux') or 14 _platform.startswith('darwin') or 15 ('bsd' in _platform)) 16 17 18def _confirm_gdb(): 19 if not _unix_like: 20 raise RuntimeError('gdb support is only available on unix-like systems') 21 gdbloc = config.GDB_BINARY 22 if not (os.path.exists(gdbloc) and os.path.isfile(gdbloc)): 23 msg = ('Is gdb present? Location specified (%s) does not exist. The gdb' 24 ' binary location can be set using Numba configuration, see: ' 25 'https://numba.pydata.org/numba-doc/latest/reference/envvars.html' # noqa: E501 26 ) 27 raise RuntimeError(msg % config.GDB_BINARY) 28 # Is Yama being used as a kernel security module and if so is ptrace_scope 29 # limited? In this case ptracing non-child processes requires special 30 # permission so raise an exception. 31 ptrace_scope_file = os.path.join(os.sep, 'proc', 'sys', 'kernel', 'yama', 32 'ptrace_scope') 33 has_ptrace_scope = os.path.exists(ptrace_scope_file) 34 if has_ptrace_scope: 35 with open(ptrace_scope_file, 'rt') as f: 36 value = f.readline().strip() 37 if value != "0": 38 msg = ("gdb can launch but cannot attach to the executing program" 39 " because ptrace permissions have been restricted at the " 40 "system level by the Linux security module 'Yama'.\n\n" 41 "Documentation for this module and the security " 42 "implications of making changes to its behaviour can be " 43 "found in the Linux Kernel documentation " 44 "https://www.kernel.org/doc/Documentation/admin-guide/LSM/Yama.rst" # noqa: E501 45 "\n\nDocumentation on how to adjust the behaviour of Yama " 46 "on Ubuntu Linux with regards to 'ptrace_scope' can be " 47 "found here " 48 "https://wiki.ubuntu.com/Security/Features#ptrace.") 49 raise RuntimeError(msg) 50 51 52@overload(gdb) 53def hook_gdb(*args): 54 _confirm_gdb() 55 gdbimpl = gen_gdb_impl(args, True) 56 57 def impl(*args): 58 gdbimpl() 59 return impl 60 61 62@overload(gdb_init) 63def hook_gdb_init(*args): 64 _confirm_gdb() 65 gdbimpl = gen_gdb_impl(args, False) 66 67 def impl(*args): 68 gdbimpl() 69 return impl 70 71 72def init_gdb_codegen(cgctx, builder, signature, args, 73 const_args, do_break=False): 74 75 int8_t = ir.IntType(8) 76 int32_t = ir.IntType(32) 77 intp_t = ir.IntType(utils.MACHINE_BITS) 78 char_ptr = ir.PointerType(ir.IntType(8)) 79 zero_i32t = int32_t(0) 80 81 mod = builder.module 82 pid = cgutils.alloca_once(builder, int32_t, size=1) 83 84 # 32bit pid, 11 char max + terminator 85 pidstr = cgutils.alloca_once(builder, int8_t, size=12) 86 87 # str consts 88 intfmt = cgctx.insert_const_string(mod, '%d') 89 gdb_str = cgctx.insert_const_string(mod, config.GDB_BINARY) 90 attach_str = cgctx.insert_const_string(mod, 'attach') 91 92 new_args = [] 93 # add break point command to known location 94 # this command file thing is due to commands attached to a breakpoint 95 # requiring an interactive prompt 96 # https://sourceware.org/bugzilla/show_bug.cgi?id=10079 97 new_args.extend(['-x', os.path.join(_path, 'cmdlang.gdb')]) 98 # issue command to continue execution from sleep function 99 new_args.extend(['-ex', 'c']) 100 # then run the user defined args if any 101 new_args.extend([x.literal_value for x in const_args]) 102 cmdlang = [cgctx.insert_const_string(mod, x) for x in new_args] 103 104 # insert getpid, getpid is always successful, call without concern! 105 fnty = ir.FunctionType(int32_t, tuple()) 106 getpid = mod.get_or_insert_function(fnty, "getpid") 107 108 # insert snprintf 109 # int snprintf(char *str, size_t size, const char *format, ...); 110 fnty = ir.FunctionType( 111 int32_t, (char_ptr, intp_t, char_ptr), var_arg=True) 112 snprintf = mod.get_or_insert_function(fnty, "snprintf") 113 114 # insert fork 115 fnty = ir.FunctionType(int32_t, tuple()) 116 fork = mod.get_or_insert_function(fnty, "fork") 117 118 # insert execl 119 fnty = ir.FunctionType(int32_t, (char_ptr, char_ptr), var_arg=True) 120 execl = mod.get_or_insert_function(fnty, "execl") 121 122 # insert sleep 123 fnty = ir.FunctionType(int32_t, (int32_t,)) 124 sleep = mod.get_or_insert_function(fnty, "sleep") 125 126 # insert break point 127 fnty = ir.FunctionType(ir.VoidType(), tuple()) 128 breakpoint = mod.get_or_insert_function(fnty, 129 "numba_gdb_breakpoint") 130 131 # do the work 132 parent_pid = builder.call(getpid, tuple()) 133 builder.store(parent_pid, pid) 134 pidstr_ptr = builder.gep(pidstr, [zero_i32t], inbounds=True) 135 pid_val = builder.load(pid) 136 137 # call snprintf to write the pid into a char * 138 stat = builder.call( 139 snprintf, (pidstr_ptr, intp_t(12), intfmt, pid_val)) 140 invalid_write = builder.icmp_signed('>', stat, int32_t(12)) 141 with builder.if_then(invalid_write, likely=False): 142 msg = "Internal error: `snprintf` buffer would have overflowed." 143 cgctx.call_conv.return_user_exc(builder, RuntimeError, (msg,)) 144 145 # fork, check pids etc 146 child_pid = builder.call(fork, tuple()) 147 fork_failed = builder.icmp_signed('==', child_pid, int32_t(-1)) 148 with builder.if_then(fork_failed, likely=False): 149 msg = "Internal error: `fork` failed." 150 cgctx.call_conv.return_user_exc(builder, RuntimeError, (msg,)) 151 152 is_child = builder.icmp_signed('==', child_pid, zero_i32t) 153 with builder.if_else(is_child) as (then, orelse): 154 with then: 155 # is child 156 nullptr = ir.Constant(char_ptr, None) 157 gdb_str_ptr = builder.gep( 158 gdb_str, [zero_i32t], inbounds=True) 159 attach_str_ptr = builder.gep( 160 attach_str, [zero_i32t], inbounds=True) 161 cgutils.printf( 162 builder, "Attaching to PID: %s\n", pidstr) 163 buf = ( 164 gdb_str_ptr, 165 gdb_str_ptr, 166 attach_str_ptr, 167 pidstr_ptr) 168 buf = buf + tuple(cmdlang) + (nullptr,) 169 builder.call(execl, buf) 170 with orelse: 171 # is parent 172 builder.call(sleep, (int32_t(10),)) 173 # if breaking is desired, break now 174 if do_break is True: 175 builder.call(breakpoint, tuple()) 176 177 178def gen_gdb_impl(const_args, do_break): 179 @intrinsic 180 def gdb_internal(tyctx): 181 function_sig = types.void() 182 183 def codegen(cgctx, builder, signature, args): 184 init_gdb_codegen(cgctx, builder, signature, args, const_args, 185 do_break=do_break) 186 return cgctx.get_constant(types.none, None) 187 return function_sig, codegen 188 return gdb_internal 189 190 191@overload(gdb_breakpoint) 192def hook_gdb_breakpoint(): 193 """ 194 Adds the Numba break point into the source 195 """ 196 if not sys.platform.startswith('linux'): 197 raise RuntimeError('gdb is only available on linux') 198 bp_impl = gen_bp_impl() 199 200 def impl(): 201 bp_impl() 202 return impl 203 204 205def gen_bp_impl(): 206 @intrinsic 207 def bp_internal(tyctx): 208 function_sig = types.void() 209 210 def codegen(cgctx, builder, signature, args): 211 mod = builder.module 212 fnty = ir.FunctionType(ir.VoidType(), tuple()) 213 breakpoint = mod.get_or_insert_function(fnty, 214 "numba_gdb_breakpoint") 215 builder.call(breakpoint, tuple()) 216 return cgctx.get_constant(types.none, None) 217 return function_sig, codegen 218 return bp_internal 219