diff options
author | Lukas Larsson <lukas@erlang.org> | 2021-04-06 10:05:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-06 10:05:15 +0200 |
commit | 874d09fa7618dbdc90f03584a4e497257e7a9a50 (patch) | |
tree | 4cba5d97566972322289e297bb6df61b717a1669 | |
parent | 3f24be9694fcf599b86338be9149d2c5a197f390 (diff) | |
parent | b33f540d866de8770ebe42a0a3a580557b51aaa8 (diff) | |
download | erlang-874d09fa7618dbdc90f03584a4e497257e7a9a50.tar.gz |
Merge pull request #4657 from garazdawi/lukas/erts/lldb-macros
erts: Implement some etp macros for lldb
-rw-r--r-- | erts/etc/unix/cerl.src | 20 | ||||
-rw-r--r-- | erts/etc/unix/etp.py | 652 |
2 files changed, 672 insertions, 0 deletions
diff --git a/erts/etc/unix/cerl.src b/erts/etc/unix/cerl.src index f617736e43..adc20f5ac6 100644 --- a/erts/etc/unix/cerl.src +++ b/erts/etc/unix/cerl.src @@ -34,6 +34,8 @@ # You have to start beam in gdb using "run". # -rgdb Run the debug compiled emulator in gdb. # You have to start beam in gdb using "run". +# -lldb Run the debug compiled emulator in lldb. +# You have to start beam in lldb using "run". # -dump Dump the bt of all threads in a core. # -break F Run the debug compiled emulator in emacs and gdb and set break. # The session is started, i.e. "run" is already don for you. @@ -170,6 +172,10 @@ while [ $# -gt 0 ]; do shift GDB=gdb ;; + "-lldb") + shift + GDB=lldb + ;; "-break") shift GDB=gdb @@ -421,6 +427,20 @@ elif [ "x$GDB" = "xgdb" ]; then echo "source $ROOTDIR/erts/etc/unix/etp-commands" > $cmdfile # Fire up gdb in emacs... exec gdb $GDBBP -x $cmdfile $gdbcmd +elif [ "x$GDB" = "xlldb" ]; then + case "x$core" in + x) + beam_args=`$EXEC -emu_args_exit ${1+"$@"}` + lldbcmd="-- $beam_args" + ;; + *) + lldbcmd="--core ${core}" + ;; + esac + cmdfile="/tmp/.cerllldb.$$" + echo "env TERM=dumb" > $cmdfile + echo "command script import $ROOTDIR/erts/etc/unix/etp.py" >> $cmdfile + exec lldb -s $cmdfile $EMU_NAME $lldbcmd elif [ "x$GDB" = "xegdb" ]; then if [ "x$EMACS" = "x" ]; then EMACS=emacs diff --git a/erts/etc/unix/etp.py b/erts/etc/unix/etp.py new file mode 100644 index 0000000000..ca8a15d69d --- /dev/null +++ b/erts/etc/unix/etp.py @@ -0,0 +1,652 @@ +# coding=utf-8 +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2013-2022. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# +# +# This script was orinally written by Anthony Ramine (aka nox) in 2013 +# A lot of things have changed since then, but the same base remains. +# + +import re +import lldb +import shlex + +unquoted_atom_re = re.compile(u'^[a-zß-öø-ÿ][a-zA-Zß-öø-ÿ0-9@]*$') + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('type format add -f hex Eterm') + debugger.HandleCommand('type format add -f hex BeamInstr') + debugger.HandleCommand('type summary add -F etp.eterm_summary Eterm') + debugger.HandleCommand('command script add -f etp.processes_cmd etp-processes') + debugger.HandleCommand('command script add -f etp.process_info_cmd etp-process-info') + debugger.HandleCommand('command script add -f etp.stacktrace_cmd etp-stacktrace') + debugger.HandleCommand('command script add -f etp.stackdump_cmd etp-stackdump') + debugger.HandleCommand('command script add -f etp.eterm_cmd etp') + +#################################### +## Print all processes in the system +#################################### +def processes_cmd(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + proc = erts_proc(target) + proc_r_o = proc.GetChildMemberWithName('r').GetChildMemberWithName('o') + proc_max_ix = proc_r_o.GetChildMemberWithName('max') + proc_tab = proc_r_o.GetChildMemberWithName('tab').Cast(ProcessPtrPtr(target)) + proc_cnt = proc.GetChildMemberWithName('vola').GetChildMemberWithName('tile').GetChildMemberWithName('count').GetChildMemberWithName('counter').unsigned + invalid_proc = global_var('erts_invalid_process', target).address_of + for proc_ix in range(0, proc_max_ix.unsigned): + proc = offset(proc_ix, proc_tab).deref + if proc.unsigned != 0 and proc.unsigned != invalid_proc.unsigned: + print('---') + print(' Pix: %d' % proc_ix) + process_info(proc) + proc_cnt -= 1 + if proc_cnt == 0: + break + +############################################ +## Print process-info about a single process +############################################ +def process_info_cmd(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target)) + process_info(proc) + +def process_info(proc): + print(' Pid: %s' % eterm(proc.GetChildMemberWithName('common').GetChildMemberWithName('id'))) + print(' State: %s' % process_state(proc)) + print(' Flags: %s' % process_flags(proc)) + current = proc.GetChildMemberWithName('current') + if current.unsigned != 0 and proc.GetChildMemberWithName('state').GetChildMemberWithName('counter').unsigned & 0x800 == 0: + print(' Current function: %s' % mfa(current)) + else: + print(' Current function: %s' % 'unknown') + i = proc.GetChildMemberWithName('i') + if i.unsigned != 0: + print(' I: %s' % eterm(i)) + else: + print(' I: %s' % 'unknown') + print(' Pointer: %#x' % proc.unsigned) + +def process_state(proc): + state = proc.GetChildMemberWithName('state').unsigned + res = '' + if state & 0x80000000: + res += "GARBAGE<0x80000000> | " + if state & 0x40000000: + res += "dirty-running-sys | " + if state & 0x20000000: + res += "dirty-running | " + if state & 0x10000000: + res += "dirty-active-sys | " + if state & 0x8000000: + res += "dirty-io-proc | " + if state & 0x4000000: + res += "dirty-cpu-proc | " + if state & 0x2000000: + res += "sig-q | " + if state & 0x1000000: + res += "off-heap-msgq | " + if state & 0x800000: + res += "delayed-sys | " + if state & 0x400000: + res += "proxy | " + proxy_process = True + else: + proxy_process = False + if state & 0x200000: + res += "running-sys | " + if state & 0x100000: + res += "active-sys | " + if state & 0x80000: + res += "sig-in-q | " + if state & 0x40000: + res += "sys-tasks | " + if state & 0x20000: + res += "garbage-collecting | " + if state & 0x10000: + res += "suspended | " + if state & 0x8000: + res += "running | " + if state & 0x4000: + res += "in-run-queue | " + if state & 0x2000: + res += "active | " + if state & 0x1000: + res += "unused | " + if state & 0x800: + res += "exiting | " + if state & 0x400: + res += "free | " + if state & 0x200: + res += "in-prq-low | " + if state & 0x100: + res += "in-prq-normal | " + if state & 0x80: + res += "in-prq-high | " + if state & 0x40: + res += "in-prq-max | " + if state & 0x30 == 0x0: + res += "prq-prio-max | " + elif state & 0x30 == 0x10: + res += "prq-prio-high | " + elif state & 0x30 == 0x20: + res += "prq-prio-normal | " + else: + res += "prq-prio-low | " + if state & 0xc == 0x0: + res += "usr-prio-max | " + elif state & 0xc == 0x4: + res += "usr-prio-high | " + elif state & 0xc == 0x8: + res += "usr-prio-normal | " + else: + res += "usr-prio-low | " + if state & 0x3 == 0x0: + res += "act-prio-max" + elif state & 0x3 == 0x1: + res += "act-prio-high" + elif state & 0x3 == 0x2: + res += "act-prio-normal" + else: + res += "act-prio-low" + return res + +def process_flags(proc): + flags = proc.GetChildMemberWithName('flags').unsigned + res = '' + if flags & ~((1 << 24)-1): + res += "GARBAGE<%#x> " % (flags & ~((1 << 24)-1)) + if flags & (1 << 22): + res += "trap-exit " + if flags & (1 << 21): + res += "hibernated " + if flags & (1 << 20): + res += "dirty-minor-gc " + if flags & (1 << 19): + res += "dirty-major-gc " + if flags & (1 << 18): + res += "dirty-gc-hibernate " + if flags & (1 << 17): + res += "dirty-cla " + if flags & (1 << 16): + res += "delayed-del-proc " + if flags & (1 << 15): + res += "have-blocked-nmsb " + if flags & (1 << 14): + res += "shdlr-onln-wait-q " + if flags & (1 << 13): + res += "delay-gc " + if flags & (1 << 12): + res += "abandoned-heap-use " + if flags & (1 << 11): + res += "disable-gc " + if flags & (1 << 10): + res += "force-gc " + if flags & (1 << 9): + res += "ets-super-user " + if flags & (1 << 8): + res += "have-blocked-msb " + if flags & (1 << 7): + res += "using-ddll " + if flags & (1 << 6): + res += "distribution " + if flags & (1 << 5): + res += "using-db " + if flags & (1 << 4): + res += "need-fullsweep " + if flags & (1 << 3): + res += "heap-grow " + if flags & (1 << 2): + res += "timo " + if flags & (1 << 1): + res += "inslpqueue " + if flags & (1 << 0): + res += "hibernate-sched " + return res + +############################################ +## Print the stacktrace of a single process +############################################ +def stacktrace_cmd(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target)) + stackdump(proc, False) + +############################################ +## Print the stackdump of a single process +############################################ +def stackdump_cmd(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + proc = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(command).Cast(ProcessPtr(target)) + stackdump(proc, True) + +def stackdump(proc, dump): + stop = proc.GetChildMemberWithName('stop') + send = proc.GetChildMemberWithName('hend') + cnt = 0 + if proc.GetChildMemberWithName('state').GetChildMemberWithName('counter').unsigned & 0x8000: + print('%%%%%% WARNING: The process is currently running, so c_p->stop will not be correct') + print(F'%% Stacktrace ({send.unsigned - stop.unsigned})'); + i = proc.GetChildMemberWithName('i') + if i.unsigned != 0: + print(F'I: {eterm(i)}') + while stop.unsigned < send.unsigned: + if stop.deref.unsigned & 0x3 == 0x0 or dump: + print(F'{cnt}: {eterm(stop.deref)}') + cnt += 1 + stop = offset(1, stop) + +############################################ +## Print an eterm +############################################ +def eterm_cmd(debugger, command, result, internal_dict): + args = shlex.split(command) + target = debugger.GetSelectedTarget() + term = target.process.selected_thread.GetSelectedFrame().EvaluateExpression(args[0]).Cast(EtermPtr(target)) + if len(args) >= 2: + print(eterm(term, int(args[1]))) + else: + print(eterm(term)) + +############################################ +## Print the summary of an Eterm +############################################ +def eterm_summary(valobj, internal_dict): + if valobj.TypeIsPointerType(): + return '' + return F'{valobj.unsigned:#x} {eterm(valobj, 5)}' + +def eterm(valobj, depth = float('inf')): + val = valobj.unsigned + tag = val & 0x3 + if tag == 0x1: + return cons(valobj, depth) + elif tag == 0x2: + return boxed(valobj, depth) + elif tag == 0x3: + return imm(valobj) + elif val == 0x0: + return '<the non-value>' + elif val == 0x4: + return '<the non-value debug>' + else: + return cp(valobj) + +def cons(valobj, depth = float('inf')): + items = [] + cdr = valobj + improper = False + truncated = False + depth *= 20 + + while True: + ptr = cdr.CreateValueFromData( + "unconsed", + lldb.SBData.CreateDataFromInt(cdr.unsigned - 1), + EtermPtr(cdr.target)) + items.append((ptr.deref, depth // 20)); # Append depth, car + if ptr.deref.unsigned & 0xF == 0xF: + depth -= 1 + else: + depth -= 20 + cdr = offset(1,ptr).deref + if is_nil(cdr): + break + if cdr.unsigned & 0x1 == 0: + improper = True + break + if depth <= 1: + truncated = True + break + + if improper: + return '#ImproperList' + + ## Try to print as ascii first + chars = '' + isprintable = True + for car, car_depth in items: + if car.unsigned & 0xF == 0xF: + if car.unsigned >> 4 == 10: + chars += '\\n' + elif car.unsigned >> 4 == 9: + chars += '\\t' + else: + chars += f'{car.unsigned >> 4:c}' + else: + isprintable = False + break + isprintable = isprintable and chars.isprintable() + if isprintable: + if not truncated: + return F'"{chars}"' + else: + return F'"{chars}..."' + + ## If not printable, we print the objects + objs = [] + chars = '[' + for car, car_depth in items: + objs.append(eterm(car, car_depth)) + if not truncated: + return '[' + ','.join(objs) + ']' + else: + return '[' + ','.join(objs) + '|...]' + +def boxed(valobj, depth = float('inf')): + ptr = valobj.CreateValueFromData( + "unboxed", + lldb.SBData.CreateDataFromInt(valobj.unsigned - 2), + EtermPtr(valobj.target)) + boxed_hdr = ptr.deref.unsigned + if boxed_hdr & 0x3f == 0x00: + arity = (boxed_hdr >> 6) + terms = [] + for x in range(1, arity+1): + if depth <= 1: + terms.append('...') + break + depth -= 1 + terms.append(eterm(offset(x, ptr).deref, depth)) + res = ','.join(terms) + return F"{{{res}}}" + if boxed_hdr & 0x3c == 0x3c: + if boxed_hdr & 0xc0 == 0x0: + return "flat_map" + else: + return "hash_map" + boxed_type = (boxed_hdr >> 2) & 0xF + if boxed_type == 0xC: + return '#ExternalPid' + if boxed_type == 0xD: + return '#ExternalPort' + if boxed_type == 0x2 or boxed_type == 0x3: + return '#Bignum' + if boxed_type == 0x6: + return '#Float' + if boxed_type == 0x4: + return '#Ref' + if boxed_type == 0xE: + return '#ExternalRef' + if boxed_type == 0x5: + return '#Fun' + if boxed_type == 0x8: + return '#RefcBin' + if boxed_type == 0x9: + return '#HeapBin' + if boxed_type == 0xA: + return '#SubBin' + return F'#Boxed<{valobj.unsigned}>' + +def imm(valobj): + val = valobj.unsigned + if (val & 0x3) != 3: + return '#NotImmediate<%#x>' % val + tag = val & 0xF + if tag == 0x3: + return pid(valobj) + elif tag == 0x7: + return port(valobj) + elif tag == 0xF: + return str(val >> 4) + elif tag == 0xB: + # Immediate2 + tag2 = val & 0x3F + if tag2 == 0x0B: + return atom(valobj) + elif tag2 == 0x1B: + return F'#Catch<{val>>6:#x}>' + elif is_nil(valobj): + return '[]' + return '#UnknownImmediate<%#x>' % val + +# Continuation pointers + +def cp(valobj): + mfaptr = erts_lookup_function_info(valobj) + if mfaptr == None: + return '#Cp<%#x>' % valobj.unsigned + else: + return '#Cp<%s>' % mfa(mfaptr) + +# Pids and ports + +def pid(valobj): + val = valobj.unsigned + if (val & 0xF) == 0x3: + target = valobj.target + if etp_arch_bits(target) == 64: + if etp_big_endian(target): + data = (val >> 35) & 0x0FFFFFFF + else: + data = (val >> 4) & 0x0FFFFFFF + else: + data = pixdata2data(valobj) + return '<0.%u.%u>' % (data & 0x7FFF, (data >> 15) & 0x1FFF) + else: + return '#NotPid<%#x>' % val + +def port(valobj): + val = valobj.unsigned + if (val & 0xF) == 0x7: + target = valobj.target + if etp_arch_bits(target) == 64 and not etp_halfword(target): + if etp_big_endian(target): + data = (val >> 36) & 0x0FFFFFFF + else: + data = (val >> 4) & 0x0FFFFFFF + else: + data = pixdata2data(valobj) + return '#Port<0.%u>' % data + else: + return '#NotPort<%#x>' % val + +# Strings and atoms + +def atom(valobj): + val = valobj.unsigned + if (val & 0x3F) == 0x0B: + name = atom_name(atom_tab(valobj)) + if unquoted_atom_re.match(name): + return str(name) + else: + return quoted_name(name, "'") + else: + return '#NotAtom<%#x>' % val + +def atom_name(entry): + name = entry.GetChildMemberWithName('name') + length = entry.GetChildMemberWithName('len').unsigned + data = name.GetPointeeData(0, length).uint8s + return ''.join(map(chr, data)) + +def quoted_name(name, quote): + return quote + ''.join(map(lambda c: quoted_char(c, quote), name)) + quote + +def quoted_char(c, quote): + point = ord(c) + if c == quote: + return '\\' + quote + elif point == 0x08: + return '\\b' + elif point == 0x09: + return '\\t' + elif point == 0x0A: + return '\\n' + elif point == 0x0B: + return '\\v' + elif point == 0x0C: + return '\\f' + elif point == 0x0D: + return '\\e' + elif point >= 0x20 and point <= 0x7E or point >= 0xA0: + return c + elif (point > 0xFF): + return '#NotChar<%#x>' % c + else: + return '\\%03o' % point + +# Constants + +MI_FUNCTIONS = 13 +MI_NUM_FUNCTIONS = 0 + +def is_nil(value): + ## We handle both -5 and 0x3b as NIL values so that this script + ## works with more versions + return value.signed == -5 or value.unsigned == 0x3b + +# Types + +def Atom(target): + return target.FindFirstType('Atom') + +def Char(target): + return target.FindFirstType('char') +def CharPtr(target): + return Char(target).GetPointerType() + +def Eterm(target): + return target.FindFirstType('Eterm') +def EtermPtr(target): + return Eterm(target).GetPointerType() +def EtermPtrPtr(target): + return EtermPtr(target).GetPointerType() + +def Range(target): + return target.FindFirstType('Range') + +def BeamInstr(target): + return target.FindFirstType('BeamInstr') +def BeamInstrPtr(target): + return BeamInstr(target).GetPointerType() + +def ErtsCodeInfo(target): + return target.FindFirstType('ErtsCodeInfo') +def ErtsCodeInfoPtr(target): + return ErtsCodeInfo(target).GetPointerType() + +def Process(target): + return target.FindFirstType('Process') +def ProcessPtr(target): + return Process(target).GetPointerType() +def ProcessPtrPtr(target): + return ProcessPtr(target).GetPointerType() + +# Globals + +def erts_atom_table(target): + return global_var('erts_atom_table', target) + +def erts_proc(target): + return global_var('erts_proc', target) + +def etp_arch_bits(target): + return global_var('etp_arch_bits', target).unsigned + +def etp_big_endian(target): + return global_var('etp_endianness', target).unsigned > 0 + +def etp_halfword(target): + return False + +def the_active_code_index(target): + return global_var('the_active_code_index', target) + +# Functions + +def atom_tab(valobj): + idx = valobj.unsigned + target = valobj.target + seg = erts_atom_table(target).GetChildMemberWithName('seg_table') + slot = offset(idx >> 16, seg).deref + entry = offset(idx >> 6 & 0x3FF, slot).deref + return entry.Cast(Atom(target).GetPointerType()) + +def erts_lookup_function_info(valobj): + r = find_range(valobj) + if r is None: + return None + pc = valobj.unsigned + target = valobj.target + start = r.GetChildMemberWithName('start').Cast(EtermPtr(target)) + curr = offset(MI_FUNCTIONS, start).Cast(ErtsCodeInfoPtr(target).GetPointerType()) + prev = curr + cnt = offset(MI_NUM_FUNCTIONS, start).deref.unsigned + for x in range(0, cnt): + prev = curr + curr = offset(1, curr) + if pc < curr.deref.unsigned: + return prev.deref.GetChildMemberWithName('mfa') + return None + +def find_range(valobj): + pc = valobj.unsigned + target = valobj.target + active = the_active_code_index(target).unsigned + ranges = offset(active, global_var('r', target)) + n = ranges.GetChildMemberWithName('n').unsigned + low = ranges.GetChildMemberWithName('modules') + high = offset(n, low) + range_type = Range(target) + range_pointer_type = range_type.GetPointerType() + mid = ranges.GetChildMemberWithName('mid').Cast(range_pointer_type) + while low.unsigned < high.unsigned: + if pc < mid.GetChildMemberWithName('start').unsigned: + high = mid + elif pc > mid.GetChildMemberWithName('end').GetChildMemberWithName('counter').unsigned: + low = offset(1, mid).Cast(range_pointer_type) + else: + return mid + length = (high.unsigned - low.unsigned) // range_type.size + mid = offset(length // 2, low) + return None + +def mfa(mfa): + return '%s:%s/%d' % (eterm(mfa.GetChildMemberWithName('module')), + eterm(mfa.GetChildMemberWithName('function')), + mfa.GetChildMemberWithName('arity').unsigned) + +def pixdata2data(valobj): + pixdata = valobj.unsigned + proc = erts_proc(target) + ro = proc.GetChildMemberWithName('r').GetChildMemberWithName('o') + pix_mask = ro.GetChildMemberWithName('pix_mask').unsigned + pix_cl_mask = ro.GetChildMemberWithName('pix_cl_mask').unsigned + pix_cl_shift = ro.GetChildMemberWithName('pix_cl_shift').unsigned + pix_cli_mask = ro.GetChildMemberWithName('pix_cli_mask').unsigned + pix_cli_shift = ro.GetChildMemberWithName('pix_cli_shift').unsigned + data = pixdata & ~pix_mask + data |= (pixdata >> pix_cl_shift) & pix_cl_mask + data |= (pixdata & pix_cli_mask) << pix_cli_shift + return data + +# LLDB utils + +def global_var(name, target): + return target.FindGlobalVariables(name, 1)[0] + +def offset(i, valobj): + # print("offset(" + str(i) + ", " + str(valobj.unsigned) + ")") + val = valobj.GetChildAtIndex(i, lldb.eNoDynamicValues, True) + if valobj.TypeIsPointerType(): + return val.address_of.Cast(valobj.GetType()) + else: + return val |