openlang

What the canary check looks like at -O2

Three small C functions, gcc -O2 -fstack-protector-strong -S -masm=intel, x86-64 Linux.

#include <stdio.h>

int read_line(FILE *f) {
    char buf[64];
    return fgets(buf, sizeof buf, f) != NULL;
}

int read_count(void) {
    int n = 0;
    return scanf("%d", &n) == 1 ? n : -1;
}

int add(int a, int b) { return a + b; }

read_line and read_count get the same prologue. mov rax, QWORD PTR fs:40 loads the guard. The next store puts it in the frame. Then xor eax, eax clears the register so the guard does not stay in it. Before ret the saved copy is loaded back and checked against fs:40 again, with sub here and xor in older listings. A failed check jumps to a block that calls __stack_chk_fail. objdump prints the same operand as %fs:0x28. The offset doesn't change, decimal in one and hex in the other.

add gets nothing. No array, no address taken.

With plain -fstack-protector, read_line keeps its canary and read_count loses it. The GCC manual gives the reason. Strong adds functions with local arrays or references to local frame addresses, and &n is one.

fs:40 is where glibc keeps the guard in the thread control block. It is not part of the calling convention. Some distribution compilers turn strong on by default, so the baseline listing needs -fno-stack-protector.

后续讨论 1

顺着彼此的依据读下去
  1. 固定链接

    Folklore says a char array gets a canary. The manual says functions that call alloca and functions with buffers of 8 bytes or more, and that 8 is --param ssp-buffer-size. Shrink buf and read_line drops out of plain -fstack-protector as well.

    #include <stdio.h>
    
    int read_short(FILE *f) {
        char buf[4];
        return fgets(buf, sizeof buf, f) != NULL;
    }

    Plain gives read_short no guard. Strong does, because strong takes any local array regardless of size. The other knob worth adding to your list is -mstack-protector-guard=global, which loads the guard from __stack_chk_guard instead of fs:40, so the offset in your listing is the default for the target and not something the compiler is stuck with.

同一个问题的其他角度

  1. Callbacks from C lose the arena I set up

    A C library calls back into my Odin code, and the callback can't see the context I set up before registering it. A proc "c" g…

    回复 8最近活动 odinc
  2. Clearing one status flag with |= cleared all of them

    |= on a write-1-to-clear status register clears more than the bit you named. The overrun flag clears by writing 1 to it. Writ…

    回复 5最近活动 c
  3. Unwrapping a C pointer with .? is an assertion

    .? on a pointer that came back from C is an assertion, not a check. It says null is impossible here. In a safety-checked buil…

    回复 5最近活动 zig