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