Можно ли определить, является ли символ переменной или функцией в C?
Я реализую некоторые ограниченные функции удаленной отладки для приложения, написанного на C, работающего на Linux. Цель состоит в том, чтобы связаться с приложением и найти значение произвольной переменной или запустить произвольную функцию.
Я могу искать символы через dlsym()
звонки, но я не могу определить, относится ли возвращаемый адрес к функции или переменной. Есть ли способ определить информацию о наборе с помощью этой таблицы символов?
5 ответов
Вы можете прочитать файл /proc/self/maps
и анализируем первые три поля каждой строки:
<begin-addr>-<end-addr> rwxp ...
Затем вы ищете строку, содержащую адрес, который вы ищете, и проверяете права доступа:
r-x
: это код;rw-
: это записываемые данные;r--
: это данные только для чтения;- любая другая комбинация: что-то странное (
rwxp
: сгенерированный код,...).
Например, следующая программа:
#include <stdio.h>
void foo() {}
int x;
int main()
{
int y;
printf("%p\n%p\n%p\n", foo, &x, &y);
scanf("%*s");
return 0;
}
... в моей системе выдает такой вывод:
0x400570
0x6009e4
0x7fff4c9b4e2c
... и это соответствующие строки из /proc/<pid>/maps
:
00400000-00401000 r-xp 00000000 00:1d 641656 /tmp/a.out
00600000-00601000 rw-p 00000000 00:1d 641656 /tmp/a.out
....
7fff4c996000-7fff4c9b7000 rw-p 00000000 00:00 0 [stack]
....
Итак, адреса: код, данные и данные.
На платформах x86 вы можете проверить инструкции, используемые для настройки стека для функции, если вы можете посмотреть в ее адресное пространство. Обычно это:
push ebp
mov ebp, esp
Я не уверен в платформах x64, но думаю, что это похоже:
push rbp
mov rbp, rsp
Это описывает соглашение о вызове C
Имейте в виду, однако, что оптимизация компилятора может оптимизировать эти инструкции. Если вы хотите, чтобы это работало, вам, возможно, придется добавить флаг, чтобы отключить эту оптимизацию. Я полагаю, что для GCC -fno-omit-frame-pointer сработает.
Одним из возможных решений является извлечение таблицы символов для приложения путем анализа вывода утилиты nm. nm содержит информацию о типе символа. Символы с типом T (глобальный текст) являются функциями.
Проблема с этим решением состоит в том, что вы должны убедиться, что ваша таблица символов соответствует цели (особенно, если вы собираетесь использовать ее для извлечения адресов, хотя использование ее в сочетании с dlsym() будет более безопасным). Метод, который я использовал для обеспечения того, чтобы сделать генерацию таблицы символов частью процесса сборки как этап постобработки.
Это может быть сделано путем объединения dlsym()
а также dladdr1()
,
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
int symbolType(void *sym) {
ElfW(Sym) *pElfSym;
Dl_info i;
if (dladdr1(sym, &i, (void **)&pElfSym, RTLD_DL_SYMENT))
return ELF32_ST_TYPE(pElfSym->st_info);
return 0;
}
int main(int argc, char *argv[]) {
for (int i=1; i < argc; ++i) {
printf("Symbol [%s]: ", argv[i]);
void *mySym = dlsym(RTLD_DEFAULT, argv[i]);
// This will not work with symbols that have a 0 value, but that's not going to be very common
if (!mySym)
puts("not found!");
else {
int type = symbolType(mySym);
switch (type) {
case STT_FUNC: puts("Function"); break;
case STT_OBJECT: puts("Data"); break;
case STT_COMMON: puts("Common data"); break;
/* get all the other types from the elf.h header file */
default: printf("Dunno! [%d]\n", type);
}
}
}
return 0;
}
Я думаю, это не очень надежный метод, но он может работать:
Возьмите адрес хорошо известной функции, такой как main()
и адрес общеизвестной глобальной переменной.
Теперь возьмите адрес неизвестного символа и вычислите абсолютное значение разницы между этим адресом и двумя другими. Наименьшее различие будет означать, что неизвестный адрес ближе к функции или к глобальной переменной, а это означает, что, вероятно, это другая функция или другая глобальная переменная.
Этот метод работает в предположении, что компилятор / компоновщик упакует все глобальные переменные в определенный блок памяти, а все функции - в другой блок памяти. Компилятор Microsoft, например, поместил все глобальные переменные перед (нижние адреса в виртуальной памяти) функциями.
Я предполагаю, что вы не захотите проверять локальные переменные, так как адрес которых не может быть возвращен функцией (после завершения функции локальная переменная теряется)