Skip to content

Commit 0b4f23b

Browse files
committed
gdbtool: also provides photon_init/photon_fini/photon_fr for living process debugging
1 parent 693f24c commit 0b4f23b

1 file changed

Lines changed: 88 additions & 119 deletions

File tree

tools/photongdb.py

Lines changed: 88 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -379,25 +379,23 @@ def state_name(self):
379379

380380
def get_saved_registers(self):
381381
"""
382-
Restore registers from saved stack.
383-
Stack layout (set by Stack::init):
384-
[Stack._ptr + 0] = saved_rbp (or th pointer)
385-
[Stack._ptr + 8] = return_addr (rip)
382+
Get saved registers from stack.
383+
Stack layout (saved by switch_context):
384+
[Stack._ptr + 0] = saved_rbp (push %rbp)
385+
[Stack._ptr + 8] = return_addr (call's push)
386+
[Stack._ptr + 16] = caller's stack frame
387+
388+
To enable GDB bt, rsp must skip switch_context's frame entirely.
389+
Returns: (rsp, rbp, rip) for caller's context
386390
"""
387391
sp = self.stack_ptr()
388392
if sp == 0:
389393
return None, None, None
390394

391-
if is_aarch64():
392-
# AArch64 layout may differ, needs confirmation
393-
rbp = read_ptr(sp)
394-
rip = read_ptr(sp + 8)
395-
rsp = sp + 16
396-
else:
397-
# x86-64
398-
rbp = read_ptr(sp)
399-
rip = read_ptr(sp + 8)
400-
rsp = sp + 16
395+
# Read saved values
396+
rbp = read_ptr(sp) # caller's rbp
397+
rip = read_ptr(sp + 8) # return address (in caller)
398+
rsp = sp + 16 # skip switch_context's frame, point to caller's stack
401399

402400
return rsp, rbp, rip
403401

@@ -1070,21 +1068,19 @@ def format_backtrace_frame(idx, addr):
10701068

10711069
class PhotonLs(gdb.Command):
10721070
"""
1073-
List all Photon threads in current vCPU (similar to GDB 'info threads').
1071+
List all Photon threads (similar to GDB 'info threads').
1072+
Works for both live process and coredump.
10741073
Usage: photon_ls
10751074
"""
10761075

10771076
def __init__(self):
10781077
gdb.Command.__init__(self, "photon_ls", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
10791078

10801079
def invoke(self, arg, tty):
1081-
global _selected_thread_idx, _in_photon_mode, _photon_threads
1080+
global _selected_thread_idx
10821081

1083-
# Use _photon_threads if in photon mode, otherwise load from current vCPU only
1084-
if _in_photon_mode and _photon_threads:
1085-
threads = _photon_threads
1086-
else:
1087-
threads = load_photon_threads_current_vcpu()
1082+
# Use collect_all_threads for pure memory read (works with coredump)
1083+
threads = collect_all_threads()
10881084

10891085
if not threads:
10901086
cprint('WARNING', "No photon threads found")
@@ -1108,13 +1104,13 @@ def invoke(self, arg, tty):
11081104

11091105
# vCPU info
11101106
extra_info = ""
1111-
if 'vcpu' in t:
1112-
extra_info += f" vCPU ({t['vcpu']:#x})"
1107+
if 'gdb_thread' in t and 'vcpu_addr' in t:
1108+
extra_info += f" vCPU {t['gdb_thread']} ({t['vcpu_addr']:#x})"
11131109

11141110
# Frame info
11151111
frame_str = format_frame_brief(rip)
1116-
if state == 'RUNNING' and rip == 0:
1117-
frame_str = "(no context available)"
1112+
if state == 'CURRENT' and rip == 0:
1113+
frame_str = "(use photon_init to enable register-based debugging)"
11181114

11191115
# Format like: * 1 Thread 0x555... RUNNING vCPU 0 (LWP in thread 1) main ()
11201116
print(f"{marker} {i:<4} {color}Thread {addr:#x}{bcolors.ENDC} "
@@ -1228,8 +1224,10 @@ def invoke(self, arg, tty):
12281224
class PhotonFr(gdb.Command):
12291225
"""
12301226
Select a Photon thread and switch to its registers (similar to GDB 'thread' command).
1227+
Requires photon_init to be called first to save original registers.
12311228
Usage: photon_fr <index>
12321229
After selection, you can use GDB commands like 'bt', 'frame', 'print' on the thread's context.
1230+
Use photon_fini to restore original registers when done.
12331231
"""
12341232

12351233
def __init__(self):
@@ -1241,11 +1239,13 @@ def invoke(self, arg, tty):
12411239
if not require_living_process("photon_fr"):
12421240
return
12431241

1244-
# Use _photon_threads if in photon mode, otherwise load from current vCPU
1245-
if _in_photon_mode and _photon_threads:
1246-
threads = _photon_threads
1247-
else:
1248-
threads = load_photon_threads_current_vcpu()
1242+
# Must be in photon mode (photon_init called)
1243+
if not _in_photon_mode:
1244+
cprint('ERROR', "Please run 'photon_init' first to save registers")
1245+
return
1246+
1247+
# Get threads list - use collect_all_threads for consistent indexing
1248+
threads = collect_all_threads()
12491249

12501250
if not threads:
12511251
cprint('WARNING', "No photon threads found")
@@ -1273,152 +1273,104 @@ def invoke(self, arg, tty):
12731273

12741274
# Build extra info string
12751275
extra_info = ""
1276-
if 'vcpu' in t:
1277-
extra_info += f" vCPU ({t['vcpu']:#x})"
1276+
if 'gdb_thread' in t and 'vcpu_addr' in t:
1277+
extra_info += f" vCPU {t['gdb_thread']} ({t['vcpu_addr']:#x})"
12781278

12791279
# Show switched message like GDB
12801280
print(f"[Switching to Thread {idx}, {t['addr']:#x} ({color}{state}{bcolors.ENDC}){extra_info}]")
12811281

1282+
# Determine which registers to use
1283+
if state == 'CURRENT':
1284+
# For CURRENT thread, use saved registers from photon_init
1285+
rsp = _saved_registers['rsp']
1286+
rbp = _saved_registers['rbp']
1287+
rip = _saved_registers['rip']
1288+
else:
1289+
# For other threads, use registers read from stack
1290+
rsp = t.get('rsp')
1291+
rbp = t.get('rbp')
1292+
rip = t.get('rip')
1293+
12821294
# Actually switch to the thread's registers
1283-
if t.get('rsp') and t.get('rbp') and t.get('rip'):
1284-
if switch_to_thread_registers(t['rsp'], t['rbp'], t['rip']):
1295+
if rsp and rbp and rip:
1296+
if switch_to_thread_registers(rsp, rbp, rip):
12851297
# Show top frame after switching
1286-
print(format_backtrace_frame(0, t['rip']))
1298+
print(format_backtrace_frame(0, rip))
12871299
else:
12881300
cprint('ERROR', "Failed to switch to thread's registers")
12891301
else:
12901302
cprint('WARNING', "No saved register context available for this thread")
12911303

12921304

12931305
# =============================================================================
1294-
# Legacy commands (for compatibility) - Now with full functionality
1306+
# Register manipulation commands (live process only)
12951307
# =============================================================================
12961308

1297-
def load_photon_threads_current_vcpu():
1298-
"""Load photon threads from current vCPU only (for photon_init/fr)"""
1299-
global _photon_threads
1300-
_photon_threads = []
1301-
1302-
# Get current thread (which gives us current vCPU)
1303-
current = get_current_thread()
1304-
if current == 0:
1305-
return []
1306-
1307-
# Get current vCPU from current thread
1308-
th = PhotonThread(current)
1309-
current_vcpu = th.vcpu()
1310-
1311-
if current_vcpu == 0:
1312-
return []
1313-
1314-
# Collect threads from current vCPU only
1315-
threads = []
1316-
visited = set()
1317-
1318-
# From sleepq
1319-
for t_addr in get_sleepq_threads(current_vcpu):
1320-
if t_addr not in visited:
1321-
visited.add(t_addr)
1322-
t = PhotonThread(t_addr)
1323-
rsp, rbp, rip = t.get_saved_registers()
1324-
threads.append({
1325-
'addr': t_addr,
1326-
'state': t.state_name(),
1327-
'rsp': rsp,
1328-
'rbp': rbp,
1329-
'rip': rip,
1330-
'vcpu': current_vcpu
1331-
})
1332-
1333-
# From standbyq
1334-
standbyq_offset = VCPU_OFFSETS.get('standbyq', 56)
1335-
standbyq_head = read_ptr(current_vcpu + standbyq_offset)
1336-
if standbyq_head:
1337-
for t_addr in traverse_list(standbyq_head, 'next'):
1338-
if t_addr not in visited:
1339-
visited.add(t_addr)
1340-
t = PhotonThread(t_addr)
1341-
rsp, rbp, rip = t.get_saved_registers()
1342-
threads.append({
1343-
'addr': t_addr,
1344-
'state': t.state_name(),
1345-
'rsp': rsp,
1346-
'rbp': rbp,
1347-
'rip': rip,
1348-
'vcpu': current_vcpu
1349-
})
1350-
1351-
# Current thread
1352-
if current not in visited:
1353-
visited.add(current)
1354-
t = PhotonThread(current)
1355-
rsp, rbp, rip = t.get_saved_registers()
1356-
threads.append({
1357-
'addr': current,
1358-
'state': 'CURRENT',
1359-
'rsp': rsp,
1360-
'rbp': rbp,
1361-
'rip': rip,
1362-
'vcpu': current_vcpu
1363-
})
1364-
1365-
_photon_threads = threads
1366-
return threads
1367-
1368-
13691309
class PhotonInit(gdb.Command):
1370-
"""Save current registers and enter photon thread lookup mode"""
1310+
"""Save current registers and enter photon thread debug mode.
1311+
After calling this, use photon_fr to switch between threads.
1312+
Use photon_fini or photon_rst to restore registers when done."""
13711313
def __init__(self):
13721314
gdb.Command.__init__(self, "photon_init", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
13731315
def invoke(self, arg, tty):
13741316
global _in_photon_mode
13751317
if not require_living_process("photon_init"):
13761318
return
13771319

1378-
threads = load_photon_threads_current_vcpu()
1320+
if _in_photon_mode:
1321+
cprint('WARNING', "Already in photon debug mode. Use photon_fini to exit first.")
1322+
return
1323+
1324+
threads = collect_all_threads()
13791325
if not threads:
13801326
cprint('WARNING', "No photon threads found")
13811327
return
13821328

13831329
if save_registers():
13841330
_in_photon_mode = True
1385-
cprint('WARNING', f"Entered photon thread lookup mode ({len(threads)} threads). "
1386-
"PLEASE do not step-in or continue before `photon_fini`")
1331+
cprint('WARNING', f"Entered photon debug mode ({len(threads)} threads). "
1332+
"Use photon_fr to switch threads. Do NOT step/continue before photon_fini!")
13871333
else:
13881334
cprint('ERROR', "Failed to save registers")
13891335

13901336

13911337
class PhotonFini(gdb.Command):
1392-
"""Restore registers and exit photon thread lookup mode"""
1338+
"""Restore original registers and exit photon debug mode."""
13931339
def __init__(self):
13941340
gdb.Command.__init__(self, "photon_fini", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
13951341
def invoke(self, arg, tty):
1396-
global _in_photon_mode, _photon_threads
1342+
global _in_photon_mode, _photon_threads, _selected_thread_idx
13971343
if not require_living_process("photon_fini"):
13981344
return
13991345

14001346
if not _in_photon_mode:
1401-
cprint('WARNING', "Not in photon mode")
1347+
cprint('WARNING', "Not in photon debug mode")
14021348
return
14031349

14041350
if restore_registers():
14051351
_in_photon_mode = False
14061352
_photon_threads = []
1407-
cprint('WARNING', "Finished photon thread lookup mode.")
1353+
_selected_thread_idx = 0
1354+
cprint('INFO', "Exited photon debug mode. Registers restored.")
14081355
else:
14091356
cprint('ERROR', "Failed to restore registers")
14101357

14111358

14121359
class PhotonRst(gdb.Command):
1413-
"""Restore registers (alias for photon_fini without clearing state)"""
1360+
"""Restore saved registers without exiting photon debug mode.
1361+
Use this to quickly return to original context while staying in debug mode."""
14141362
def __init__(self):
14151363
gdb.Command.__init__(self, "photon_rst", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
14161364
def invoke(self, arg, tty):
14171365
if not require_living_process("photon_rst"):
14181366
return
14191367

1368+
if not _saved_registers:
1369+
cprint('WARNING', "No saved registers. Run photon_init first.")
1370+
return
1371+
14201372
if restore_registers():
1421-
cprint('INFO', "Registers restored")
1373+
cprint('INFO', "Registers restored (still in photon debug mode)")
14221374
else:
14231375
cprint('ERROR', "Failed to restore registers")
14241376

@@ -1458,6 +1410,23 @@ def invoke(self, arg, tty):
14581410
# Initialization
14591411
# =============================================================================
14601412

1413+
# Event handler: auto-restore registers before program continues
1414+
def _on_cont(event):
1415+
"""Called when program is about to continue (step/next/continue).
1416+
Auto-restore registers if in photon debug mode."""
1417+
global _in_photon_mode, _photon_threads, _selected_thread_idx
1418+
if _in_photon_mode and _saved_registers:
1419+
cprint('WARNING', "Auto-restoring registers before continue...")
1420+
restore_registers()
1421+
_in_photon_mode = False
1422+
_photon_threads = []
1423+
_selected_thread_idx = 0
1424+
1425+
try:
1426+
gdb.events.cont.connect(_on_cont)
1427+
except:
1428+
pass # Older GDB versions may not support this
1429+
14611430
PhotonLs()
14621431
PhotonBt()
14631432
PhotonPs()
@@ -1469,4 +1438,4 @@ def invoke(self, arg, tty):
14691438

14701439
cprint('INFO', 'Photon-GDB-extension v2 loaded')
14711440
cprint('INFO', 'Commands: photon_ls, photon_fr <n>, photon_bt [n], photon_ps, photon_current')
1472-
cprint('INFO', 'Use photon_fr to select thread, then photon_bt to show its backtrace')
1441+
cprint('INFO', 'Register commands: photon_init -> photon_fr -> photon_fini')

0 commit comments

Comments
 (0)