Skip to content

Instantly share code, notes, and snippets.

Last active February 17, 2024 12:40
Show Gist options
  • Save caltuntas/b84eda2937acfcfef2097a192a9d5995 to your computer and use it in GitHub Desktop.
Save caltuntas/b84eda2937acfcfef2097a192a9d5995 to your computer and use it in GitHub Desktop.
docker build . -t musl-unwinder
docker run -dit \
--name musl-unwinder \
--ulimit core=-1 --privileged \
--security-opt seccomp=unconfined \
--entrypoint '/bin/sh' \
docker exec -it musl-unwinder sh
FROM alpine:3.18.0
RUN apk --no-cache add \
gdb \
lldb \
build-base \
libc-dev \
elfutils \
file \
binutils \
coreutils \
gcc \
py3-lldb \
COPY main.c Makefile /tmp
CMD ["/bin/sh"]
#include <stdio.h>
#include <stdlib.h>
int number=0;
int mul(int num, int times) {
printf("Multiplying numbers: %d,%d\n", num, times);
if (number > 10) {
return num * times;
int sub(int num, int sub) {
printf("Subtracting numbers: %d,%d\n", num, sub);
int a = num - sub;
int b = 2;
return mul(a,b);
int add(int a, int b) {
printf("Adding numbers: %d,%d\n", a, b);
int c = a + b;
int d = 5;
return sub(c, d);
int main(int argc, char* argv[]) {
printf("Number: %d\n", number);
int result = add(3, 7);
printf("Result: %d\n", result);
return 0;
main: main.c
$(CC) $(CFLAGS) -o main main.c
import re
import gdb
from gdb.unwinder import Unwinder
def debug(pc, current_rsp, offset, addr, frame_id, func):
print('{:<20}:{:<8}'.format('pc', str(pc)))
print('{:<20}:{:<8}'.format('current_rsp', str(current_rsp)))
print('{:<20}:{:<8}'.format('offset', str(offset)))
print('{:<20}:{:<8}'.format('return address', hex(addr)))
print('{:<20}:{:<8}'.format('frame_id', str(frame_id)))
u64_ptr = gdb.lookup_type('unsigned long long').pointer()
class FrameID:
def __init__(self, sp, pc):
self.sp = sp
self.pc = pc
def __str__(self):
return f'sp: {self.sp}, pc: {self.pc}'
class MuslUnwinder(Unwinder):
def __init__(self):
def is_musl_frame(self,pc):
obj = gdb.execute("info symbol 0x%x" % pc, False, True)
return "musl" in obj
def dereference(self,adr):
deref = gdb.parse_and_eval("0x%x" % adr).cast(u64_ptr).dereference()
return deref
def __call__(self, pending_frame):
pc = pending_frame.read_register("pc")
if not self.is_musl_frame(pc):
return None
asm = gdb.execute("disassemble 0x%x" % pc, False, True)
lines = asm.splitlines()
func = None
args_bytes = 0
locals_bytes = 0
rbp_bytes = 0
for line in lines:
m = re.match('Dump of assembler code for function (.*):', line)
if m:
func =
elif re.match('.*push[ ]*%', line):
args_bytes += 8
if "rbp" in line:
rbp_bytes += 8
elif m := re.match('.*sub[ ]*\\$0x([A-Fa-f0-9]+),%rsp', line):
locals_bytes = int(, 16)
offset = locals_bytes + args_bytes
current_rsp = pending_frame.read_register("rsp")
current_rbp = pending_frame.read_register("rbp")
rsp = current_rsp + offset + 8
return_addr = self.dereference(current_rsp + offset)
frame_id = FrameID(rsp, pc)
unwind_info = pending_frame.create_unwind_info(frame_id)
unwind_info.add_saved_register("rsp", rsp)
unwind_info.add_saved_register("rip", return_addr)
if rbp_bytes > 0:
saved_rbp = self.dereference(current_rsp+locals_bytes+rbp_bytes)
unwind_info.add_saved_register("rbp", saved_rbp)
unwind_info.add_saved_register("rbp", current_rbp)
if gdb.parameter("verbose"):
debug(pc, current_rsp, offset, return_addr, frame_id, func)
return unwind_info
gdb.execute('set disassembly-flavor att')
gdb.unwinder.register_unwinder(None, MuslUnwinder(), replace=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment