I want to trace the goid of go programs using ebpf
.
After reading for some posts and blogs, I know that %fs:0xfffffffffffffff8
points to the g
struct of go and mov %fs:0xfffffffffffffff8,%rcx
instruction always appear at the start of a go function.
Taking main.main
as an example:
func main() {
177341 458330: 64 48 8b 0c 25 f8 ff mov %fs:0xfffffffffffffff8,%rcx
177342 458337: ff ff
177343 458339: 48 3b 61 10 cmp 0x10(%rcx),%rsp
177344 45833d: 76 1a jbe 458359 <main.main+0x29>
177345 45833f: 48 83 ec 08 sub $0x8,%rsp
177346 458343: 48 89 2c 24 mov %rbp,(%rsp)
177347 458347: 48 8d 2c 24 lea (%rsp),%rbp
177348 myFunc()
177349 45834b: e8 10 00 00 00 callq 458360 <main.myFunc>
177350 }
I also know the goid information is stored in the g
struct of go. The value of fs register can be obtained via the ctx
argument of ebpf
function.
But I don't know what the real address of %fs:0xfffffffffffffff8
because I am new to assembly language. Could anyone give me some hints?
If the value of fs register were 0x88, what is the value of %fs:0xfffffffffffffff8
?
That's a negative number, so it's one qword before the FS base. You need the FS base address, which is not the selector value in the FS segment register that you could see with a debugger.
Your process probably made a system call to ask the OS to set it, or possibly used the wrfsbase
instruction at some point on systems that support it.
Note that at least outside of Go, Linux typically uses FS for thread-local storage.
(I'm not sure what the standard way to actually find the FS base is; it's obviously OS dependent to do that in user-space where rdmsr
isn't available; FS and GS base are exposed as MSRs, so OSes use that instead of actually modifying a GDT or LDT entry. rdfsbase
needs to be enabled by the kernel setting a bit in CR4 on CPUs that support the FSGSBASE ISA extension, so you can't count on that working.)
@MargaretBloom suggests that user-space could trigger an invalid page fault; most OSes report the faulting virtual address back to user-space. In Linux for example, SIGSEGV has the address. (Or SIGBUS if it was non-canonical, IIRC. i.e. not in the low or high 47 bits of virtual address space, but in the "hole" where the address isn't the sign-extension of the low 48.)
So you'd want to install signal handlers for those signals and try a load from an offset that (with 0 base) would be in the middle of kernel space, or something like that. If for some reason that doesn't fault, increment the virtual address by 1TiB or something in a loop. Normally no MMIO is mapped into user-space's virtual address space so there are no side effects for merely reading.