Согласно Что такое трюк LD_PRELOAD?
, LD_PRELOAD
- это переменная среды, которая позволяет пользователям загружать библиотеку до загрузки любой другой библиотеки, включая libc.so
.
Из этого определения это означает 2 вещи:
Библиотека, указанная в LD_PRELOAD
, может перегружать символы из другой библиотеки.
Однако, если указанная библиотека не содержит символа, другие библиотеки будут искать этот символ как обычно.
Здесь вы указали LD_PRELOAD
как lib_override.so
, он определяет int ret_42(void)
и глобальную переменную ptr
и w
, но он не определяет int ret_global(void)
.
Таким образом, int ret_global(void)
будет загружен из lib.so
, и эта функция будет напрямую возвращать 42
, поскольку компилятор не видит возможности, что ptr
и w
из lib.c
могут быть изменены во время выполнения (они будут вставьте int const data
section в elf
, linux
, гарантируя, что они не могут быть изменены во время выполнения аппаратной защитой памяти), поэтому компилятор оптимизировал это для непосредственного возврата 42
.
Редактировать - тест:
Итак, я сделал некоторые изменения в вашем скрипте:
#!/bin/sh -eu
: ${CC:=gcc}
cat > lib.c <<EOF
int ret_42(void) { return 42; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
EOF
cat > lib_override.c <<EOF
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
EOF
cat > main.c <<EOF
#include <stdio.h>
int ret_42(void), ret_global(void);
struct wrap_ { const int x; };
extern struct wrap { const struct wrap_ *ptr; } const w;
int main(void)
{
printf("ret_42()=%d\n", ret_42());
printf("ret_global()=%d\n", ret_global());
printf("w.ptr->x=%d\n",w.ptr->x);
}
EOF
for c in *.c; do gcc -fpic -O2 $c -c; done
$CC lib.o -o lib.so -shared
$CC lib_override.o -o lib_override.so -shared
$CC main.o $PWD/lib.so
export LD_LIBRARY_PATH=$PWD
./a.out
LD_PRELOAD=$PWD/lib_override.so ./a.out
И на этот раз он печатает:
ret_42()=42
ret_global()=42
w.ptr->x=42
ret_42()=50
ret_global()=60
w.ptr->x=60
Редактировать - вывод:
Получается, что вы либо перегружаете все связанные детали, либо ничего не перегружаете, иначе вы получите такое хитрое поведение. Другой подход заключается в определении int ret_global(void)
в заголовке, а не в динамической библиотеке, поэтому вам не придется беспокоиться об этом, когда вы пытаетесь перегрузить некоторые функции для выполнения некоторых тестов.
Редактировать - объяснение, почему int ret_global(void)
перегружается, а ptr
и w
- нет.
Во-первых, я хочу указать на тип символов, определенных вами (используя приемы из Как мне перечислить символы в файле .so
Файл lib.so
:
Symbol table '.dynsym' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000001110 6 FUNC GLOBAL DEFAULT 12 ret_global
6: 0000000000001120 17 FUNC GLOBAL DEFAULT 12 ret_fn_result
7: 000000000000114c 0 FUNC GLOBAL DEFAULT 14 _fini
8: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
9: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
10: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
Symbol table '.symtab' contains 28 entries:
Num: Value Size Type Bind Vis Ndx Name
23: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
24: 0000000000001110 6 FUNC GLOBAL DEFAULT 12 ret_global
25: 0000000000001120 17 FUNC GLOBAL DEFAULT 12 ret_fn_result
26: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
27: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
Файл lib_override.so
:
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
6: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
7: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
8: 0000000000001108 0 FUNC GLOBAL DEFAULT 13 _init
9: 0000000000001120 0 FUNC GLOBAL DEFAULT 14 _fini
10: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
Symbol table '.symtab' contains 26 entries:
Num: Value Size Type Bind Vis Ndx Name
23: 0000000000001100 6 FUNC GLOBAL DEFAULT 12 ret_42
24: 0000000000003018 8 OBJECT GLOBAL DEFAULT 22 w
25: 0000000000000200 4 OBJECT GLOBAL DEFAULT 1 ptr
Вы обнаружите, что, несмотря на то, что оба символа являются GLOBAL
, все функции отмечены как тип FUNC
, который является перегружаемым, в то время как все переменные имеют тип OBJECT
. Тип OBJECT
означает, что он не перегружен, поэтому компилятору не нужно использовать разрешение символов для получения данных.
Для получения дополнительной информации об этом, отметьте это: Что такое «условные» символы?
.