From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 9021A6EC44; Fri, 13 Nov 2026 02:43:01 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 9021A6EC44 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1794526981; bh=kUsPHKhPBCEKCfDTcZXoQZepTGBhwSUlvmnLE23aRRg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=Sg79hUhPdFPyUmgWTGtIu3+OiQkN66+60WhMlj2uwPbPKGFEoldJuYz3zgwtmpVUg 7BKwzBFhhEaGR+0XOgG4jXPMOmCGZ03V7NJBYPx0nc7Oge1pcXNpMARocVHmwWWeab 29icE5hoKOyVPfx588x59hWdngOA1gVV4dl89fBs= Received: from send126.i.mail.ru (send126.i.mail.ru [89.221.237.221]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id B7B086EC44 for ; Fri, 13 Nov 2026 02:42:31 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org B7B086EC44 Received: by exim-smtp-5cfb6649f-vjfzp with esmtpa (envelope-from ) id 1xReR4-00000000Ioc-1J0i; Fri, 13 Nov 2026 02:42:31 +0300 To: Sergey Kaplun , Sergey Bronnikov , Evgeniy Temirgaleev Date: Fri, 13 Nov 2026 02:42:22 +0300 Message-ID: <0a9a648552eb3a3bbaa9639e0d567ae008cb0e14.1794522533.git.m.elhimov@vk.team> X-Mailer: git-send-email 2.55.0 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Mailru-Src: smtp X-618D5548: E5D60092197968DBC7D0F6C13AE992135ED581263DE2408472197B577EDF39BE925922C9AB70D97F X-7564579A: B8F34718100C35BD X-77F55803: 4F1203BC0FB41BD9089E34853A70B9B0A1835D0C9E402EC34B08160B080890E7182A05F5380850401373771C3F8D412A3DE06ABAFEAF6705F1A6BDF76B0B1065C4E5300F674211BB8B3203750C901C87 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE749E89BD568380EECC2099A533E45F2D0395957E7521B51C2CFCAF695D4D8E9FCEA1F7E6F0F101C6759CC434672EE6371C2A783ECEC0211ADC4224003CC836476D5A39DEEDB180909611E41BBFE2FEB2B0FAF6A55B3782F321B6DD91595FBE6410D63FB1DA2D6AB79A67FD9C840810BD30C819B32DFABC24A725E5C173C3A84C3E478A468B35FE76776E601842F6C81A127C277FBC8AE2E8BEC1A2CCC669F7C933AA81AA40904B5D99C9F4D5AE37F343AD1F44FA8B9022EA23BBE47FD9DD3FB595F5C1EE8F4F765FC72CEEB2601E22B093A03B725D353964B0B7D0EA88DDEDAC722CA9DD8327EE493EFA58DFCB7B0AE11F67BA0E7924E9B8DC4224003CC83647689D4C264860C145E X-C1DE0DAB: 0D63561A33F958A5EFCD375D532853385002B1117B3ED6969716061699C2CCE5B2920F75BA9A967F823CB91A9FED034534781492E4B8EEAD7F04F3DDCB452CA6C79554A2A72441328621D336A7BC284946AD531847A6065A535571D14F44ED41 X-C8649E89: 1C3962B70DF3F0AD73CAD6646DEDE191716CD42B3DD1D34C77DD89D51EBB774225B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D65983DBEB968BCC0E1E390F733F8E602EED08FE80058FD800FD6B4785E30CF4C7E35197CF7BA6E2A5FFB8341EE9D5BE9A0AC26329B344358D8E2F3E611DBA4EFD58F0F60515CCADE9009350E1BD188BD56E4C41F94D744909CE9D6718F459EE2962AE54DA86D399E591103B389C0A9E82E8 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXOcIETfglQORZ0zpDET4Zrk3igikrdHlWMEqUvd5Nog/sl0Q2O/NArY= X-Mailru-Sender: CE01F6F06FBB9B4E14759DB66A5FA98A83A7FFF9518B7613B951B70A5BD4BD8E1E9AD22CBD80A6E7F43CCAED39DA92F1EAA2F645D7D0C73F8B67290C0B8F2C0D12EEFBA914E8A1CE696B58449F085A8023028798991C5DA182F85D6DCAB44FBA5FEEDEB644C299C0ED14614B50AE0675 X-Mras: Ok Subject: [Tarantool-patches] [PATCH luajit 1/4] dbg: clean up and unification X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Mikhail Elhimov via Tarantool-patches Reply-To: Mikhail Elhimov Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" * unified `dbg.eval` approach to simplify error handling (exception in case of any failure) * removed trailing newline in all the dumpers to unify composing of the complex output * newly introduced `dbg.print` is used whenever trailing newline is needed to make code more compact and clean * moved handling of a missing command arg to command level (all the dump-like functions now expect concrete value) * unified handling of command-level error (introduced special exception for LLDB that works similar to `gdb.GdbError`, i.e. doesn't cause traceback to be printed) * in `gdb.eval()` dropped check of the value returned by `gdb.parse_and_eval()` as it would also fail for an expression like `'0'` (looks like a kind of experimental code that was left by mistake) * `lldb.eval()` is adjusted to return object of the same type as `lldb.cast()` method returns (this improves consistency) * renamed `dbg.eval` argument to reflect its meaning (it is an expression rather than a command) --- src/luajit_dbg.py | 178 +++++++++-------- .../debug-extension-tests.py | 182 +++++++++++++----- 2 files changed, 234 insertions(+), 126 deletions(-) diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py index 80057a4e..77abf8c6 100644 --- a/src/luajit_dbg.py +++ b/src/luajit_dbg.py @@ -110,6 +110,10 @@ class Debugger(object): self.write('{} command initialized\n'.format(name)) self.write('LuaJIT debug extension is successfully loaded\n') + def print(self, msg): + '''Print the message with the new line.''' + self.write(msg + '\n') + @abc.abstractmethod def cast(self, typestr, val): '''Cast the value to the required C type.''' @@ -146,8 +150,8 @@ class Debugger(object): pass @abc.abstractmethod - def eval(self, command): - '''Parse and evaluate the given debugger command.''' + def eval(self, expr): + '''Parse and evaluate the given debugger expression.''' pass @abc.abstractmethod @@ -268,14 +272,8 @@ class _GDBDebugger(Debugger): else: return None - def eval(self, command): - if not command: - return None - - ret = gdb.parse_and_eval(command) - if not ret: - raise gdb.GdbError('table argument empty') - return ret + def eval(self, expr): + return gdb.parse_and_eval(expr) def detect_arch(self): if hasattr(self, 'arch'): @@ -670,15 +668,26 @@ class _LLDBDebugger(Debugger): else: return None - def eval(self, command): - if not command: - return None - + def eval(self, expr): process = self.target.GetProcess() thread = process.GetSelectedThread() frame = thread.GetSelectedFrame() - ret = frame.EvaluateExpression(command) - return ret + val = frame.EvaluateExpression(expr) + # LLDB doesn't throw an exception if it fails to + # evaluate an expression. In order to simplify handling + # of `Debugger.eval()` it is convenient to make it work + # similar to GDB, i.e. throw an exception in case of + # failure. + # Be aware that SBValue.IsValid() is necessary, but not + # sufficient as it only indicates that the value object + # does contain the relevant data (including the potential + # error), so it is necessary to check the `error` property + # as well. + if not val.IsValid(): + raise self.error('{}: invalid expression'.format(expr)) + if val.error.fail: + raise self.error(val.error) + return lldb.value(val) def detect_arch(self): if hasattr(self, 'arch'): @@ -694,8 +703,13 @@ class _LLDBDebugger(Debugger): def write(self, msg): sys.stdout.write(msg) + class DbgError(Exception): + '''Raised to indicate an error during command execution. + Doesn't cause a traceback to be printed (like gdb.GdbError for GDB).''' + pass + def error(self, errmsg): - return Exception(errmsg) + return self.DbgError(errmsg) def check_libluajit(self): # TODO: Implement postpone loading for LLDB too. @@ -738,12 +752,27 @@ class _LLDBDebugger(Debugger): def __call__(ljbase, debugger, args, exe_ctx, result): try: ljbase.execute(args) - except Exception as e: + except _LLDBDebugger.DbgError as e: msg = 'Failed to execute command `{}`: {}'.format( ljbase.name, e ) result.SetError(msg) + # `result` is an output argument of LLDB command + # handler. If error message is set, LLDB displays + # it in session. But such a message is missing in + # output when the same problem occurs in batch + # mode, so in order to be able to catch this kind + # of error in batch mode it is needed to put it + # into stderr explicitly. + # Also to avoid duplication of the same error + # message in interactive mode, it should be done + # in batch mode only. There is no official way to + # check if LLDB is in the batch mode so one of the + # recommended workarounds is used below that + # checks if tty is not used for standard input. + if not sys.stdin.isatty(): + result.PutError(lldb.SBFile.Create(sys.stderr, True)) @abc.abstractmethod def execute(ljbase, args): @@ -2399,7 +2428,7 @@ def dump_proto(proto): startbc = proto_bc(proto) func_loc = proto_loc(proto) # Location has the following format: '{chunk}:{firstline}'. - dump = '{func_loc}-{lastline}\n'.format( + dump = '{func_loc}-{lastline}'.format( func_loc=func_loc, lastline=proto['firstline'] + proto['numline'], ) @@ -2408,9 +2437,9 @@ def dump_proto(proto): return '=> ' + str(npc_from + delta).zfill(4) for bcnum in range(0, int(proto['sizebc'])): - dump += (str(bcnum).zfill(4) + ' ' + dump_bc( + dump += ('\n' + str(bcnum).zfill(4) + ' ' + dump_bc( startbc[bcnum], jmp_format=jmp_format, jmp_ctx=bcnum, proto=proto, - ) + '\n') + )) return dump @@ -2421,9 +2450,9 @@ def dump_func(func): pt = funcproto(func) return dump_proto(pt) elif ffid == 1: - return 'C function @ {}\n'.format(strx64(func['f'])) + return 'C function @ {}'.format(strx64(func['f'])) else: - return 'fast function #{}\n'.format(int(ffid)) + return 'fast function #{}'.format(int(ffid)) # FFI dumpers. @@ -2740,7 +2769,7 @@ def dump_irins(irins, trace=None): if op1 != -1: args = dump_call_args(trace, int(op1)) - return '{flags} {type} {name:6} [{mode:2}] {f}({args}) {ct}\n'.format( + return '{flags} {type} {name:6} [{mode:2}] {f}({args}) {ct}'.format( flags=flags, name=irname, mode=ir_mode(irop), @@ -2792,7 +2821,7 @@ def dump_irins(irins, trace=None): else: typename = IRTYPES[irt_type(irt)] - return '{flags} {type} {name:6} [{mode:2}] {left:<9s} {right}\n'.format( + return '{flags} {type} {name:6} [{mode:2}] {left:<9s} {right}'.format( flags=flags, name=irname, mode=ir_mode(irop), @@ -2857,18 +2886,19 @@ def dump_regsp(irins, ins_number): def dump_trace(trace, flags): - dump = 'Trace {num} start\n\tproto: {start_pt}\n\tBC: {start_bc}\n'.format( + dump = 'Trace {num} start\n\tproto: {start_pt}\n\tBC: {start_bc}'.format( num=trace['traceno'], start_pt=gcref(trace['startpt']), start_bc=mref('BCIns *', trace['startpc']), ) nins = trace['nins'] - REF_BIAS - dump += '---- TRACE IR\n' + dump += '\n---- TRACE IR' nsnap = 0 snap = trace['snap'][nsnap] snapref = snap['ref'] for irnum in range(1, nins): + dump += '\n' irref = REF_BIAS + irnum if 's' in flags and irref >= snapref and nsnap < trace['nsnap']: dump += '.... ' @@ -2894,27 +2924,32 @@ def dump_tref(tref): ) -def dump_jslots(coroutine): - lstate = L(None) - g = G(lstate or coroutine) - j = J(g) - - dump = '' - maxslot = j['baseslot'] + j['maxslot'] +def dump_jslots(J): + dump = [] + maxslot = J['baseslot'] + J['maxslot'] first_base_slot = 1 + LJ_FR2 for n in reversed(range(first_base_slot, maxslot)): - tref = j['slot'][n] + tref = J['slot'][n] ref = tref_ref(tref) address = dbg.address(tref) - dump += '{addr} {nslot:04d} {base:1s} {tref}{const}\n'.format( + dump.append('{addr} {nslot:04d} {base:1s} {tref}{const}'.format( addr=address, - base='B' if address == j['base'] else ' ', + base='B' if address == J['base'] else ' ', nslot=n, tref=dump_tref(tref), - const=' ' + dump_irk(j['cur'], ref - REF_BIAS) - if ref != 0 and ref < REF_BIAS else '' - ) - return dump + const=' ' + dump_irk(J['cur'], ref - REF_BIAS) + if ref != 0 and ref < REF_BIAS else '' + )) + return '\n'.join(dump) + + +# Extension commands helpers. #################################### + + +def cast_command_arg(typestr, arg): + if not arg: + raise dbg.error('Argument is required') + return dbg.cast(typestr, dbg.eval(arg)) # Extension commands. ############################################ @@ -2931,8 +2966,8 @@ compile-time flag to inspect if LuaJIT is built in dual-number mode. ''' def execute(self, arg): - dbg.write( - 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}\n' + dbg.print( + 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}' .format( LJ_64=LJ_64, LJ_GC64=LJ_GC64, @@ -2964,9 +2999,7 @@ https://github.com/tarantool/tarantool/wiki/LuaJIT-Bytecodes. ''' def execute(self, arg): - dbg.write('{}\n'.format( - dump_bc(dbg.cast('BCIns *', dbg.eval(arg))[0]) - )) + dbg.print(dump_bc(cast_command_arg('BCIns *', arg)[0])) class LJDumpCType(dbg.LJBase): @@ -2978,9 +3011,7 @@ and dumps the ID and the name for this C data type. ''' def execute(self, arg): - dbg.write('{}\n'.format( - dump_ctype(dbg.cast('CType *', dbg.eval(arg))) - )) + dbg.print(dump_ctype(cast_command_arg('CType *', arg))) class LJDumpFunc(dbg.LJBase): @@ -3005,7 +3036,7 @@ function: ''' def execute(self, arg): - dbg.write('{}'.format(dump_func(dbg.cast('GCfuncC *', dbg.eval(arg))))) + dbg.print(dump_func(cast_command_arg('GCfuncC *', arg))) class LJGC(dbg.LJBase): @@ -3029,7 +3060,7 @@ The command requires no args and dumps current GC stats: def execute(self, arg): g = G(L(None)) - dbg.write('GC stats: {state}\n{stats}\n'.format( + dbg.print('GC stats: {state}\n{stats}'.format( state=gc_state(g), stats=dump_gc(g) )) @@ -3060,8 +3091,7 @@ error message occurs. ''' def execute(self, arg): - gcobj = dbg.cast('GCobj *', dbg.eval(arg)) - dbg.write('{}\n'.format(dump_gcobj(gcobj))) + dbg.print(dump_gcobj(cast_command_arg('GCobj *', arg))) class LJDumpIR(dbg.LJBase): @@ -3078,7 +3108,7 @@ https://github.com/tarantool/tarantool/wiki/LuaJIT-SSA-IR. ''' def execute(self, arg): - dbg.write('{}'.format(dump_irins(dbg.cast('IRIns *', dbg.eval(arg))))) + dbg.print(dump_irins(cast_command_arg('IRIns *', arg))) class LJDumpJSlots(dbg.LJBase): @@ -3095,9 +3125,8 @@ when there is no coroutine to be inspected in the debugged frame. ''' def execute(self, arg): - dbg.write('{}'.format( - dump_jslots(dbg.cast('lua_State *', dbg.eval(arg))) - )) + lstate = L(dbg.eval(arg) if arg else None) + dbg.print(dump_jslots(J(G(lstate)))) class LJDumpProto(dbg.LJBase): @@ -3122,9 +3151,7 @@ function: ''' def execute(self, arg): - dbg.write('{}'.format( - dump_proto(dbg.cast('GCproto *', dbg.eval(arg))) - )) + dbg.print(dump_proto(cast_command_arg('GCproto *', arg))) class LJDumpStack(dbg.LJBase): @@ -3161,7 +3188,8 @@ If L is omitted, the main coroutine is used. ''' def execute(self, arg): - dbg.write('{}\n'.format(dump_stack(L(dbg.eval(arg))))) + lstate = L(dbg.eval(arg) if arg else None) + dbg.print(dump_stack(lstate)) class LJState(dbg.LJBase): @@ -3175,13 +3203,13 @@ The command requires no args and dumps current VM and GC states: def execute(self, arg): g = G(L(None)) - dbg.write('{}\n'.format('\n'.join( + dbg.print('\n'.join( map(lambda t: '{} state: {}'.format(*t), { 'VM': vm_state(g), 'GC': gc_state(g), 'JIT': jit_state(g), }.items()) - ))) + )) class LJDumpString(dbg.LJBase): @@ -3196,8 +3224,8 @@ is replaced with the corresponding error when decoding fails. ''' def execute(self, arg): - string = dbg.cast('GCstr *', dbg.eval(arg)) - dbg.write("String: {body} [{len} bytes] with hash {hash}\n".format( + string = cast_command_arg('GCstr *', arg) + dbg.print("String: {body} [{len} bytes] with hash {hash}".format( body=strdata(string), hash=strx64(string['hash']), len=string['len'], @@ -3217,7 +3245,7 @@ The command receives a GCtab address and dumps the table contents: ''' def execute(self, arg): - t = dbg.cast('GCtab *', dbg.eval(arg)) + t = cast_command_arg('GCtab *', arg) array = mref('TValue *', t['array']) nodes = mref('struct Node *', t['node']) mt = gcval(t['metatable']) @@ -3227,22 +3255,22 @@ The command receives a GCtab address and dumps the table contents: } if mt != 0: - dbg.write('Metatable detected: {}\n'.format(strx64(mt))) + dbg.print('Metatable detected: {}'.format(strx64(mt))) - dbg.write('Array part: {} slots\n'.format(capacity['apart'])) + dbg.print('Array part: {} slots'.format(capacity['apart'])) for i in range(capacity['apart']): slot = array + i - dbg.write('{ptr}: [{index}]: {value}\n'.format( + dbg.print('{ptr}: [{index}]: {value}'.format( ptr=slot, index=i, value=dump_tvalue(slot) )) - dbg.write('Hash part: {} nodes\n'.format(capacity['hpart'])) + dbg.print('Hash part: {} nodes'.format(capacity['hpart'])) # See hmask comment in lj_obj.h for i in range(capacity['hpart']): node = nodes + i - dbg.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}\n'.format( + dbg.print('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format( ptr=node, key=dump_tvalue(node['key']), val=dump_tvalue(node['val']), @@ -3280,8 +3308,7 @@ error message occurs. ''' def execute(self, arg): - tv = dbg.cast('TValue *', dbg.eval(arg)) - dbg.write('{}\n'.format(dump_tvalue(tv))) + dbg.print(dump_tvalue(cast_command_arg('TValue *', arg))) class LJDumpTrace(dbg.LJBase): @@ -3300,10 +3327,7 @@ Trace may be preceded with /FLAGS: def execute(self, arg): arg, flags = dbg.extract_flags(arg, 'rs') - dbg.write('{}'.format(dump_trace( - dbg.cast('GCtrace *', dbg.eval(arg)), - flags - ))) + dbg.print(dump_trace(cast_command_arg('GCtrace *', arg), flags)) def load(event=None): diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py index ab44121c..fe8c5028 100644 --- a/test/tarantool-debugger-tests/debug-extension-tests.py +++ b/test/tarantool-debugger-tests/debug-extension-tests.py @@ -19,30 +19,6 @@ LLDB = 'lldb' in DEBUGGER EXTENSION = EXTENSION_PATH + '/luajit_dbg.py' TIMEOUT = 10 -if LLDB: - INFERIOR_ARGS = '--' - LOAD_EXTENSION = 'command script import ' + EXTENSION - PROCESS_RUN = ( - # Prevent errors in case when running tests in Docker. - 'settings set target.disable-aslr false\n' - 'process launch' - ) - # Don't run any initialization scripts. - RUN_CMD_FILE = [ - '--batch', - '--no-lldbinit', - '--no-use-colors', - '--source-quietly', - '--source' - ] -else: - # GDB. - INFERIOR_ARGS = '--args' - LOAD_EXTENSION = 'source ' + EXTENSION - PROCESS_RUN = 'run' - # Don't run any initialization scripts. - RUN_CMD_FILE = ['--batch', '--nx', '--quiet', '--command'] - TEST_VERBOSE = os.getenv('DEBUGGER_TEST_VERBOSE', default=False) RX_ADDR = r'0x[a-f0-9]+' @@ -60,7 +36,7 @@ def persist(data): return tmp -def execute_process(cmd, timeout=TIMEOUT): +def execute_process_raw(cmd, timeout=TIMEOUT): if LEGACY: # XXX: The Python 2.7 version of `subprocess.Popen` # doesn't have a timeout option, so the required @@ -68,12 +44,12 @@ def execute_process(cmd, timeout=TIMEOUT): process = subprocess.Popen( cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, # This prevents sending of SIGSTTOU to the test when # running by `make'. Stdin is unused anyway. stdin=subprocess.DEVNULL ) - timer = Timer(TIMEOUT, process.kill) + timer = Timer(timeout, process.kill) timer.start() stdout, _ = process.communicate() timer.cancel() @@ -87,16 +63,71 @@ def execute_process(cmd, timeout=TIMEOUT): process = subprocess.run( cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, # This prevents sending of SIGSTTOU to the test when # running by `make'. Stdin is unused anyway. stdin=subprocess.DEVNULL, universal_newlines=True, - timeout=TIMEOUT + timeout=timeout, ) return process.stdout +if LLDB: + INFERIOR_ARGS = '--' + LOAD_EXTENSION = 'command script import ' + EXTENSION + PROCESS_RUN = ( + # Prevent errors in case when running tests in Docker. + 'settings set target.disable-aslr false\n' + 'process launch' + ) + # Don't run any initialization scripts. + RUN_CMD_FILE = [ + '--batch', + '--no-lldbinit', + '--no-use-colors', + '--source-quietly', + '--source' + ] + SET_EXEC = 'target create ' + LUAJIT_BINARY + SET_RUN_ARGS = 'settings set target.run-args' + + def execute_process(cmd, timeout=TIMEOUT): + # While executing extension commands LLDB might produce + # errors that cannot be caught in extension. The internal + # parser does some processing in background and might + # emit an error message into stderr asynchronously. Also + # the internal parser has a kind of fallback mechanism + # that might resolve the problem, but is not able to + # "cancel" the error message which might fail tests in + # this case. + # + # List of regex that specifies lines to be wiped out from + # the LLDB output. In case additional cleaning is needed, + # just append this list with the correponding regex (use + # as strict regex as possible in order to avoid accidental + # removing of relevant data). + RX_LINES_TO_REMOVE = [ + # This error occurs while parsing `union FPRCBArg` (it + # looks like either offset of `f`` field or size of + # `union FPRCBArg` is identified incorrectly). + r".*DW_TAG_member 'f' refers to type \w+ which extends beyond.*", + ] + raw_out = execute_process_raw(cmd, timeout) + return re.sub(r'^({})\n'.format('|'.join(RX_LINES_TO_REMOVE)), + '', raw_out, flags=re.M) + +else: + # GDB. + INFERIOR_ARGS = '--args' + LOAD_EXTENSION = 'source ' + EXTENSION + PROCESS_RUN = 'run' + # Don't run any initialization scripts. + RUN_CMD_FILE = ['--batch', '--nx', '--quiet', '--command'] + SET_EXEC = 'file ' + LUAJIT_BINARY + SET_RUN_ARGS = 'set args' + execute_process = execute_process_raw + IS_DUALNUM = execute_process([ LUAJIT_BINARY, '-e', "print(require('ffi').abi('dualnum'))" ]).strip() == 'true' @@ -129,42 +160,38 @@ else: class TestCaseBase(unittest.TestCase): @classmethod - def construct_cmds(cls): - return '\n'.join([ + def construct_cmds(cls, deferred, *run_args): + cmds = [ + SET_EXEC, + SET_RUN_ARGS + ' ' + ' '.join(run_args), 'b {loc}'.format(loc=cls.location), PROCESS_RUN, 'n', - LOAD_EXTENSION, cls.extension_cmds.strip(), - 'q', - ]) + ] + cmds.insert(0 if deferred else 1, LOAD_EXTENSION) + return '\n'.join(cmds) - @classmethod - def setUpClass(cls): - cmd_file = persist(cls.construct_cmds()) - script_file = persist(cls.lua_script) + def check(self, deferred=False): + script_file = persist(self.lua_script) + cmd_file = persist(self.construct_cmds(deferred, script_file.name)) process_cmd = [ DEBUGGER, *RUN_CMD_FILE, cmd_file.name, - INFERIOR_ARGS, - LUAJIT_BINARY, - script_file.name, ] if TEST_VERBOSE: - print('# Test name: {}'.format(cls.__name__)) + print('# Test name: {}'.format(self.__class__.__name__)) print('# Test command: {}'.format(' '.join(process_cmd))) - cls.output = execute_process(process_cmd) + output = execute_process(process_cmd) if TEST_VERBOSE: - print('# Command output:\n{}'.format(cls.output)) + print('# Command output:\n{}'.format(output)) cmd_file.close() script_file.close() - - def check(self): if LEGACY: - self.assertRegexpMatches(self.output, self.pattern.strip()) + self.assertRegexpMatches(output, self.pattern.strip()) else: - self.assertRegex(self.output, self.pattern.strip()) + self.assertRegex(output, self.pattern.strip()) # Test that the emitted debug information supports macro @@ -1051,8 +1078,65 @@ class TestLJCTypeBase(TestCaseBase): pattern = r'\[\d+\] ' -for test_cls in TestCaseBase.__subclasses__(): +class TestMissingArgBase(TestCaseBase): + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = r'Argument is required\n' + + @classmethod + def setUpClass(cls): + cls.extension_cmds = '{}\n'.format(cls.cmd_name) + super(TestMissingArgBase, cls).setUpClass() + + +class TestMissingArgLJFunc(TestMissingArgBase): + cmd_name = 'lj-func' + + +class TestMissingArgLJTValue(TestMissingArgBase): + cmd_name = 'lj-tv' + + +class TestMissingArgLJBC(TestMissingArgBase): + cmd_name = 'lj-bc' + + +class TestMissingArgLJStr(TestMissingArgBase): + cmd_name = 'lj-str' + + +class TestMissingArgLJTable(TestMissingArgBase): + cmd_name = 'lj-tab' + + +class TestMissingArgLJProto(TestMissingArgBase): + cmd_name = 'lj-proto' + + +class TestMissingArgLJGCO(TestMissingArgBase): + cmd_name = 'lj-gco' + + +class TestMissingArgLJCType(TestMissingArgBase): + cmd_name = 'lj-ctype' + + +def get_leaf_subclasses(cls): + subclasses = cls.__subclasses__() + if not subclasses: + yield cls + else: + for sub in subclasses: + for leaf in get_leaf_subclasses(sub): + yield leaf + + +for test_cls in get_leaf_subclasses(TestCaseBase): test_cls.test = lambda self: self.check() + test_cls.test_with_deferred_loading = \ + unittest.skipIf(LLDB, "not supported for LLDB")( + lambda self: self.check(True) + ) if __name__ == '__main__': unittest.main(verbosity=2) -- 2.55.0