Skip to content

Instantly share code, notes, and snippets.

@aclements
Last active January 17, 2017 16:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aclements/8d2d6a1d1ade4bc4fd492db6d3eb07a5 to your computer and use it in GitHub Desktop.
Save aclements/8d2d6a1d1ade4bc4fd492db6d3eb07a5 to your computer and use it in GitHub Desktop.
Clone a core file with a new PC/SP for thread 1
import gdb
import re
import tempfile
import os
import subprocess
import shutil
import struct
NOTE_HDR = struct.Struct("III")
USER_REGS = struct.Struct(27*"Q")
NT_PRSTATUS = 1
def command(fn):
name = fn.__name__.replace('_', '-')
dct = {
'__doc__': fn.__doc__,
'__init__': lambda self: super(cls, self).__init__(name, gdb.COMMAND_USER),
'invoke': lambda self, *args, **kw: fn(*args, **kw),
}
cls = type(fn.__name__ + 'Cls', (gdb.Command,), dct)
cls()
return fn
@command
def core_with_pc_sp(arg, from_tty):
"""core-with-pc-sp pc sp: create a modified core file with thread 1 at PC/SP
Only works with linux/amd64."""
args = arg.split()
if len(args) != 2:
raise gdb.GdbError("expected args: pc sp")
pc, sp = map(gdb.parse_and_eval, args)
corepath = current_core_path()
tmpcorefd, tmpcorepath = tempfile.mkstemp()
tmpcore = os.fdopen(tmpcorefd, "w+b")
shutil.copyfileobj(open(corepath, "rb"), tmpcore)
tmpcore.flush()
# Get notes.
notes = subprocess.check_output(["readelf", "--notes", corepath])
found = False
for off, length in re.findall(r"^Displaying notes found at file offset (0x[0-9a-fA-F]+) with length (0x[0-9a-fA-F]+):$", notes, re.M):
off, length = int(off, 16), int(length, 16)
tmpcore.seek(off)
for doff, name, typ, desc in parse_notes(tmpcore.read(length)):
#print(doff, name, typ, len(desc), desc)
if name == "CORE" and typ == NT_PRSTATUS:
found = True
break
if found:
break
if not found:
raise gdb.GdbError("no NT_PRSTATUS note in core file")
# Desc is a struct elf_prstatus. Regs are at
# offset 0x70, size 0xd8 and are a
# user_regs_struct.
regs = list(USER_REGS.unpack(desc[0x70:0x70+0xd8]))
# RIP is register 16, RSP is register 19.
regs[16], regs[19] = pc, sp
desc = desc[:0x70] + USER_REGS.pack(*regs) + desc[0x70+0xd8:]
# Write out modified registers set.
tmpcore.seek(off + doff)
tmpcore.write(desc)
# It would be nice to use GDB's inferiors support to switch to
# this core and dump from it, but add-inferior is completely
# broken. So for now just print the modified core file's path
# and let the user inspect it.
print(tmpcorepath)
def current_core_path():
target = gdb.execute("info target", to_string=True)
cores = re.findall(r"^Local core dump file:\n\s*`(.*)', file type", target, re.M)
if len(cores) == 0:
raise gdb.GdbError("Not a core file")
elif len(cores) > 1:
raise gdb.GdbError("`info target' reported multiple core files")
return cores[0]
def parse_notes(data):
noff = 0
while noff < len(data):
# The ELF64 spec is full of lies. These are all 4 byte fields
# or 4 byte aligned, not 8 byte.
namesz, descsz, typ = NOTE_HDR.unpack_from(data[noff:])
noff += NOTE_HDR.size
# Read name.
name = data[noff:noff+namesz-1] # Remove terminating nul (another lie)
noff = (noff+namesz+3) & ~3
# Read data.
desc = data[noff:noff+descsz]
doff = noff
noff = (noff+descsz+3) & ~3
yield doff, name, typ, desc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment