Search code examples
goassemblyx86-64bpfebpf

What is the real address of `%fs:0xfffffffffffffff8`?


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?


Solution

  • 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.