7

I was debugging an irrelevant issue in the Linux kernel and saw the etcd process, which was managed by supervisor, was repeatedly hitting page fault exception and receiving SIGSEGV.

I got curious and used objdump to disassemble the program, and found the faulting amd64 instruction to be:

89 04 25 00 00 00 00    mov    %eax,0x0

I then looked at the disassembly of a hello world program. I saw a very common pattern in code generated by go compiler, that is at the end of a function, right after ret, there's a mov followed by a jmp back into the function. For example,

0000000000400c00 <main.main>:
  400c00:       64 48 8b 0c 25 f0 ff    mov    %fs:0xfffffffffffffff0,%rcx
  400c07:       ff ff
        ...
  400c4b:       48 83 7c 24 48 00       cmpq   $0x0,0x48(%rsp)
  400c51:       74 59                   je     400cac <main.main+0xac>
  400c53:       48 c7 04 24 c0 fc 47    movq   $0x47fcc0,(%rsp)
  400c5a:       00

        ...
  400cab:       c3                      retq
  400cac:       89 04 25 00 00 00 00    mov    %eax,0x0
  400cb3:       eb 9e                   jmp    400c53 <main.main+0x53>

Is this some trick played by go? If so, how does it work? I'm guessing at 0x400c51 it jumps to 0x400cac, triggers a SIGSEGV, which is handled and then the next instruction jumps back to 0x400c53.

Forge
  • 6,538
  • 6
  • 44
  • 64
Wei Hu
  • 2,888
  • 2
  • 27
  • 28
  • 2
    `mov %eax,0x0` doesn't move a register to an immediate. It moves a register to address 0 (unless there was a fixup done at some point that replaced 0 with something else). If you wanted to attempt to move `eax` to the immediate 0 (which wouldn't assemble) you would write `mov %eax, $0` (with a dollar sign). – Michael Mar 17 '16 at 07:29
  • @Michael, thanks for the correction. I got confused. – Wei Hu Mar 17 '16 at 07:34
  • no compiler will set a register to 0 by mov in optimized mode. They'll use [`xor reg, reg`](http://stackoverflow.com/q/33666617/995714) – phuclv Mar 17 '16 at 07:36
  • 1
    @LưuVĩnhPhúc: This is AT&T syntax assembly. The operand order is `src, dest`. – Michael Mar 17 '16 at 07:37
  • 1
    I think asking this question on the official user list has better chance of getting response from the core Go developers: https://groups.google.com/d/forum/golang-nuts – kostya Mar 17 '16 at 08:32
  • is there some code writing an adress to 400caf before jumping to 400cac? – Tommylee2k Mar 17 '16 at 10:34
  • 2
    It could be [this](https://github.com/golang/go/blob/master/src/runtime/proc.go#L203) which is used to cause a crash if exit should somehow fail. Useful when porting Go to a new platform. – thwd Mar 17 '16 at 10:51
  • While `@thwd`'s comment is probably a spot on, please do what `@kostya` suggested and post a message to the mailing list: AFAIK, none of the Go core devs read this SO tag anyway but they do frequent the mailing list. – kostix Mar 17 '16 at 15:37

1 Answers1

1

I got some answers from the Go developers: https://groups.google.com/forum/#!topic/golang-nuts/_7yio3ZfVBE

Basically, this pattern is the nil check in the obsolete implementation. Quoted is the answer from Keith Randall.

If the pointer is nil, it jumps to an instruction that generates a fault. That fault is used to start a nil ptr panic.

It's a pretty inefficient code sequence. The jmps never appear to be used. Upgrade to a more recent Go version and you'll see it has been improved.

Wei Hu
  • 2,888
  • 2
  • 27
  • 28
  • 1
    gcc often puts short blocks of code after the first `ret` in a function for the body of an `if` statement in C. So when the condition is true, it jumps down there and does something, then jumps back. This makes the not-true case more efficient, because there's no taken conditional branch and no unconditional branch. It looks like the Go compiler used the same structure for code that was meant to crash, not return. That's my guess as to why that pattern existed in the first place: it was natural for gcc to make code like that for `if(null pointer) panic` – Peter Cordes Mar 18 '16 at 19:43