Last active
January 17, 2017 16:55
-
-
Save aclements/8d2d6a1d1ade4bc4fd492db6d3eb07a5 to your computer and use it in GitHub Desktop.
Clone a core file with a new PC/SP for thread 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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