Три маленькие функции на C, 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 и read_count получают одинаковый пролог. mov rax, QWORD PTR fs:40 загружает страж. Следующая запись кладёт его в кадр. Затем xor eax, eax очищает регистр, чтобы страж в нём не остался. Перед ret сохранённая копия загружается обратно и сверяется с fs:40, здесь через sub, в старых листингах через xor. При несовпадении - переход в блок, который вызывает __stack_chk_fail. objdump печатает тот же операнд как %fs:0x28. Смещение одно, в одном случае десятичное, в другом шестнадцатеричное.

add не получает ничего. Ни массива, ни взятого адреса.

С обычным -fstack-protector у read_line канарейка остаётся, у read_count пропадает. Причина есть в руководстве GCC. Strong добавляет функции с локальными массивами или ссылками на адреса в локальном кадре, а &n как раз такая ссылка.

fs:40 указывает на место, где glibc хранит страж в блоке управления потоком. Это не часть соглашения о вызовах. Некоторые дистрибутивные компиляторы включают strong по умолчанию, так что для базового листинга нужен -fno-stack-protector.