Go runtime value dumping (#2329)

* basic go value dumping

* better error handling and misc improvements

* minor documentation changes

* satisfy mypy

* struct parsing and bug fix

* satisfy mypy

* deal with evacuated buckets

* better error message for invalid expressions

* convert bytearray to bytes before repr

* support for recursive types and better type dumping

* better QOL for go-dump command

* formatting options and debug/pretty printing

* add go dumping unit tests

* deal with differences in old go version

* lint

* old go versions missing any alias

* proper name dumping for go versions prior to 1.17

* lint

* go is being weird on CI

* warn instead of erroring

* function and interface dumping
pull/2366/head
Jason An 1 year ago committed by GitHub
parent b188baa2c7
commit 94ee021f42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -713,6 +713,7 @@ def load_commands() -> None:
import pwndbg.commands.elf
import pwndbg.commands.flags
import pwndbg.commands.ghidra
import pwndbg.commands.godbg
import pwndbg.commands.got
import pwndbg.commands.got_tracking
import pwndbg.commands.heap

@ -0,0 +1,95 @@
from __future__ import annotations
import argparse
import pwndbg.commands
import pwndbg.gdblib.arch
import pwndbg.gdblib.config
import pwndbg.gdblib.godbg
import pwndbg.gdblib.memory
import pwndbg.gdblib.regs
from pwndbg.commands import CommandCategory
parser = argparse.ArgumentParser(
description="Dumps a Go value of a given type at a specified address."
)
parser.add_argument(
"ty",
type=str,
help="Go type of value to dump, e.g. map[int]string, or the address of a type to resolve at runtime, e.g. 0x408860",
)
parser.add_argument(
"address",
type=pwndbg.commands.AddressExpr,
help="Address to dump",
)
parser.add_argument("-x", "--hex", action="store_true", help="Display non-pointer integers as hex")
parser.add_argument(
"-f",
"--decimals",
nargs="?",
type=int,
help="Configures the number of decimal places to display for floating points",
)
parser.add_argument(
"-d",
"--debug",
action="store_true",
help="Shows debug info, like addresses for slice/map elements, slice capacity, etc.",
)
parser.add_argument(
"-p",
"--pretty",
action="store_true",
help="Enables pretty printing",
)
@pwndbg.commands.ArgparsedCommand(
parser, category=CommandCategory.MEMORY, command_name="go-dump", aliases=["god"]
)
@pwndbg.commands.OnlyWhenRunning
def go_dump(
ty: str, address: int, hex: bool, decimals: int | None, debug: bool, pretty: bool
) -> None:
try:
ty_addr = int(ty, 0)
(_, parsed_ty) = pwndbg.gdblib.godbg.decode_runtime_type(ty_addr)
if parsed_ty is None:
print("Failed to decode runtime type.")
return
except ValueError:
parsed_ty = pwndbg.gdblib.godbg.parse_type(ty)
fmt = pwndbg.gdblib.godbg.FormatOpts(
int_hex=hex, float_decimals=decimals, debug=debug, pretty=pretty
)
print(parsed_ty.dump(address, fmt))
parser = argparse.ArgumentParser(
description="Dumps a Go runtime reflection type at a specified address."
)
parser.add_argument(
"address",
type=pwndbg.commands.AddressExpr,
help="Address to dump",
)
@pwndbg.commands.ArgparsedCommand(
parser, category=CommandCategory.MEMORY, command_name="go-type", aliases=["goty"]
)
@pwndbg.commands.OnlyWhenRunning
def go_type(address: int) -> None:
meta, ty = pwndbg.gdblib.godbg.decode_runtime_type(address, True)
print(f" Name: {meta.name}")
print(f" Kind: {meta.kind.name}")
print(f" Size: {meta.size} ({meta.size:#x})")
print(f"Align: {meta.align}")
print(f"Parse: {ty}")
if ty:
data = ty.additional_metadata()
if data:
print("\n".join(data))

File diff suppressed because it is too large Load Diff

@ -0,0 +1,17 @@
package main
import "fmt"
func testFunc(x interface{}) *interface{} {
fmt.Println(x)
return &x // leak x to force it to be allocated somewhere
}
func main() {
testFunc(map[string]int{"a": 1, "b": 2, "c": 3})
testFunc([]struct {
a int
b string
}{{a: 1, b: "first"}, {a: 2, b: "second"}})
testFunc([3]complex64{1.1 + 2.2i, -2.5 - 5i, 4.2 - 2.1i})
}

@ -1,3 +0,0 @@
package main
func main() {}

@ -1,3 +0,0 @@
package main
func main() {}

@ -19,7 +19,7 @@ EXTRA_FLAGS_ASM =
GO = go
SOURCES_GO = $(wildcard *.go)
COMPILED_GO = $(SOURCES_GO:.go=)
COMPILED_GO = $(SOURCES_GO:.go=.x86) $(SOURCES_GO:.go=.x64)
ifeq ($(TARGET), x86)
CFLAGS += -m32
@ -54,14 +54,14 @@ all: $(LINKED) $(LINKED_ASM) $(COMPILED_GO) $(CUSTOM_TARGETS)
@echo "[+] Linking '$@'"
@$(LD) -Ttext 0x400080 --section-start .note.gnu.property=0x8000000 -o $@ $?
%.x86 : %.x86.go
%.x86 : %.go
@echo "[+] Building '$@'"
@GOARCH=386 $(GO) build $?
@GOARCH=386 $(GO) build -gcflags "-N -l" -o $@ $?
@# Not stripped on purpose
%.x64 : %.x64.go
%.x64 : %.go
@echo "[+] Building '$@'"
@GOARCH=amd64 $(GO) build $?
@GOARCH=amd64 $(GO) build -gcflags "-N -l" -o $@ $?
@# Not stripped on purpose
heap_bugs.out: heap_bugs.c

@ -8,7 +8,7 @@ GOSAMPLE_X64 = tests.binaries.get("gosample.x64")
GOSAMPLE_X86 = tests.binaries.get("gosample.x86")
def test_typeinfo_go_x64(start_binary):
def test_typeinfo_go_x64():
"""
Tests pwndbg's typeinfo knows about the Go x64 types.
Catches: Python Exception <class 'gdb.error'> No type named u8.:
@ -19,7 +19,7 @@ def test_typeinfo_go_x64(start_binary):
assert "Python Exception" not in start
def test_typeinfo_go_x86(start_binary):
def test_typeinfo_go_x86():
"""
Tests pwndbg's typeinfo knows about the Go x32 types
Catches: Python Exception <class 'gdb.error'> No type named u8.:
@ -28,3 +28,29 @@ def test_typeinfo_go_x86(start_binary):
gdb.execute("file " + GOSAMPLE_X86)
start = gdb.execute("start", to_string=True)
assert "Python Exception" not in start
def helper_test_dump(start_binary, filename):
gdb.execute("set environment GOMAXPROCS=1")
start_binary(filename)
gdb.execute("break gosample.go:6", to_string=True)
gdb.execute("continue")
first = gdb.execute("go-dump any &x", to_string=True)
assert first.strip() == """(map[string]int) &{"a": 1, "b": 2, "c": 3}"""
gdb.execute("continue")
second = gdb.execute("go-dump any &x", to_string=True)
assert (
second.strip()
== """([]struct { a int; b string }) [struct {a: 1, b: "first"}, struct {a: 2, b: "second"}]"""
)
gdb.execute("continue")
third = gdb.execute("go-dump -f 1 any &x", to_string=True)
assert third.strip() == """([3]complex64) [(1.1 + 2.2i), (-2.5 - 5.0i), (4.2 - 2.1i)]"""
def test_go_dumping_x64(start_binary):
helper_test_dump(start_binary, GOSAMPLE_X64)
def test_go_dumping_x86(start_binary):
helper_test_dump(start_binary, GOSAMPLE_X86)

Loading…
Cancel
Save