Почему адреса инструкций в верхней части пользовательского пространства памяти противоречат компоновке памяти процесса Linux?

#c #linux #64-bit

#c #linux #64-разрядная

Вопрос:

 #include <stdio.h>
void func() {}

int main() {
    printf("%p", amp;func);

    return 0;
}
  

Эта программа выводила 0x55c4cda9464a
Предположим, что это func будет сохранено в .text разделе, и, согласно этому рисунку, из CS:APP : введите описание изображения здесь
Я полагаю, что адрес func будет где-то рядом с начальным адресом .text раздела, но этот адрес находится где-то посередине. Почему это так? Локальные переменные, хранящиеся в стеке, имеют адреса около 2 ^ 48 — 1, но я пытался разобрать разные коды C, и инструкции всегда располагались где-то около этого 0x55... адреса.

Комментарии:

1. @Jabberwocky: Прекрати это. Предполагая, что люди не должны заботиться об адресах своих функций, говорят, что люди должны ожидать запуска только в идеализированной среде C и никогда не заботиться о деталях реализации. Это чушь собачья. Через некоторое время после изучения некоторых основ программирования студенты посещают курсы по операционным системам и узнают о макетах памяти. Кто-то должен поддерживать операционные системы. Кто-то должен обновить загрузчики программ. Кто-то должен диагностировать ошибки, связанные с ними. Учащимся необходимо изучить эти вещи и поэкспериментировать с ними. Не подавляйте любопытство.

2. @Jabberwocky: Я не писал, что вы написали, что ему должно быть все равно, я написал, что предлагать людям не заботиться — это плохо. При постановке вопроса явно подразумевается, что у человека должна быть причина для желания приобрести знания. Это плохое и вредное отношение. Остановите это.

3. @TortelliniTeusday Я полагаю, у вас нет точно такой же системы (какой, кстати?), как та, которую они использовали в книге. Если вы попробуете использовать GDB Online , результат будет ближе к тому, что вы ожидаете. OTOH на Ideone результат ближе к тому, что вы получаете в своей системе.

4. @TortelliniTeusday также взгляните на это , вы можете видеть, что в зависимости от размера, malloc память выделяется из совершенно разных мест.

5. @TortelliniTeusday также попробуйте скомпилировать свой код с помощью -m32 переключателя, который сгенерирует 32-битный двоичный файл, выходные данные которого снова будут совершенно другими.

Ответ №1:

gcc при настройке на --enable-default-pie 1 (который используется по умолчанию) создает независимые от позиции исполняемые файлы (PIE). Это означает, что адрес загрузки не совпадает с тем, который компоновщик указал во время компиляции (0x400000 для x86_64). Это механизм безопасности, позволяющий включить рандомизацию рандомизации адресного пространства (ASLR) 2. То есть gcc компилируется с -pie опцией по умолчанию.

Если вы скомпилируете с -no-pie опцией ( gcc -no-pie file.c ), то вы увидите, что адрес func ближе к 0x400000.

В моей системе я получаю:

 $ gcc -no-pie t.c
$ ./a.out 
0x401132
  

Вы также можете проверить адрес загрузки с помощью readelf :

 $ readelf -Wl a.out | grep LOAD
 LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x000478 0x000478 R   0x1000
 LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x0001f5 0x0001f5 R E 0x1000
 LOAD           0x002000 0x0000000000402000 0x0000000000402000 0x000158 0x000158 R   0x1000
 LOAD           0x002e10 0x0000000000403e10 0x0000000000403e10 0x000228 0x000230 RW  0x1000
  

1 вы можете проверить это с помощью gcc --verbose .

2 Вы также можете заметить, что адрес, печатаемый вашей программой, отличается при каждом запуске. Это из-за ASLR. Вы можете отключить это с помощью:

 $ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
  

ASLR включен в Linux по умолчанию.

Комментарии:

1. Интересно… но если бы это было так, то почему 0x55... адрес не был бы рандомизирован?

2. @jackw11111 Я еще не полностью прочитал ответ. Я забыл упомянуть, что происходит рандомизация, но она все еще вокруг этого адреса. Я думаю, что это только рандомизирует начало сегмента в диапазоне нескольких мегабайт, если это то, о чем вы спрашиваете.

3. @TortelliniTeusday Я могу воспроизвести это поведение и в своей системе. Повторяется повсюду 0x55 , иногда 0x56 .

4. @jackw11111 На x86, ELF_ET_DYN_BASE вычисляется примерно таким значением (если я компилирую с отключенным ASLR, начальный адрес в x86_64, который я вижу, является 0x555555555145 ). Я полагаю, что ASLR calc применяется поверх этого, но ограничен определенным количеством битов.