Как процесс Solaris может прочитать свою собственную таблицу символов?

У меня есть процесс Solaris, представляющий собой приложение C++, которое загружается ld с несколькими библиотеками .so. Это приложение имеет функцию, которая получает адрес возврата в вызывающей функции, а затем пытается определить имя указанной вызывающей функции.

Если я использую для этого dladdr(3), он не всегда помещает то, что я ожидаю увидеть в Dl_info::dli_sname. Похоже, он возвращает имя функции, которая не является ближайшей ниже или на уровне указателя. Если я возьму значение указателя и посмотрю на вывод nm, я смогу сопоставить значение с точной функцией, которую я ожидаю.

Мне интересно, есть ли способ получить карту символов для процесса и позволить ему искать имя функции без использования dladdr(3). Мне особенно интересно получить карту символов не только для самого исполняемого файла, но и для всех .so библиотек, которые он загрузил.

Я работаю на Solaris10/SPARC и использую gcc 4.2.x.

Благодарю вас!


person evolvah    schedule 11.08.2011    source источник
comment
Сканирует ли он отладочную информацию? Я помню, что XTank делал что-то подобное, но это было давным-давно, когда я смотрел на код.   -  person Martin York    schedule 11.08.2011
comment
Спасибо за комментарий, Мартин. То, о чем я говорю, — это скорее интроспективный анализ процесса в его собственной таблице символов. Я знаю, что могу использовать nm, objdump и даже pstack для просмотра этой информации, но я говорю о получении этой информации самим процессом.   -  person evolvah    schedule 12.08.2011


Ответы (2)


Я попробовал простой тест с использованием dladdr() на Solaris 10/SPARC (но предостережение: GCC 3.4, прямой C), и это отлично работает для меня:

#include <dlfcn.h>
#include <stdio.h>

void print_name(char *name, void *addr);
void print_name_by_dladdr(void *addr);

int main(int argc, const char *argv[])
{
    print_name("main", (void *)&main);
    print_name("print_name", (void *)&print_name);
    print_name("printf", (void *)&printf);
    return 0;
}

void print_name(char *name, void *addr)
{
    (void)printf("Getting name of function %s() at 0x%x\n", name, addr);
    print_name_by_dladdr(addr);
}

void print_name_by_dladdr(void *addr)
{
    Dl_info dli;
    if(!dladdr(addr, &dli)) {
        perror("dladdr()");
        exit(1);
    }
    (void)printf("  %s\n", dli.dli_sname);
}

Выход:

Getting name of function main() at 0x10714
  main
Getting name of function print_name() at 0x10778
  print_name
Getting name of function printf() at 0x209b8
  _PROCEDURE_LINKAGE_TABLE_

Это также работает правильно, если я пишу (например)

    print_name("main", (void *)&main + 4);

Вы говорите, что можете правильно разрешить вывод nm, поэтому возможности кажутся ограниченными... вы уверены, что обратный адрес выводится или правильно передается вашей функции распознавания? Я предполагаю, что вы используете для этого встроенные модули GCC? Я протестировал __builtin_return_address(0), и у меня это тоже отлично работает. Если вы используете встроенные модули GCC, вызывали ли вы __builtin_extract_return_address() (подробности см. на странице выше, явно упоминает SPARC)? Можете ли вы опубликовать свой код?

Можете ли вы немного растянуть, чтобы «процесс перечитал свои собственные двоичные/общие объектные файлы»? Если это так, то libelf может помочь. Это именно то, что используют некоторые из упомянутых вами утилит, например nm: http://cr.opensolaris.org/~devnull/6515400/usr/src/cmd/sgs/nm/common/nm.c.html

Эта вводная статья с сайта sun.com может оказаться полезной (внимание: статья 10 лет).

Это не так приятно, как нативный самоанализ, и странно, что dladdr(3C) не работает :(

Альтернативный промежуточный вариант: пробовали ли вы использовать флаг RTLD_DL_SYMENT для dladdr1(3C) (а затем, возможно, позаимствовать из nm.c, как указано выше, в возвращенном символе ELF)?

person Martin Carpenter    schedule 12.08.2011
comment
Еще раз спасибо за подробный ответ! Я использую __builtin_return_address(1) для извлечения обратного адреса в вызывающем абоненте. Однако, если я попытаюсь настроить возвращаемый адрес через __builtin_extract_return_address(), я получу ошибку компиляции. GCC жалуется на то, что __builtin_extract_return_address не объявлен в области видимости. В то же время он успешно компилирует пример приложения с __builtin_return_address(1). Я теперь в замешательстве, почему он знает об одном встроенном и не знает о другом. - person evolvah; 12.08.2011
comment
И я думаю, что вы совершенно правы, у меня возникла эта проблема из-за того, что я не использую __builtin_extract_return_address. Но я не понимаю, как GCC мог не знать о своих встроенных... - person evolvah; 12.08.2011
comment
Согласно комментарию в newae.com/6lowpan/fip/a00105.html __builtin_extract_return_address нужен как минимум gcc 4.5. Время обновить... - person Martin Carpenter; 12.08.2011
comment
Спасибо еще раз! Мне нужно будет изучить больше альтернатив. Обновление компилятора в моей среде не очень жизнеспособный вариант. Я пытаюсь заново изобрести облегченную альтернативу Dmalloc с рядом настроек, чтобы соответствовать типу проектов, с которыми я имею дело больше всего. - person evolvah; 12.08.2011

немного поздно, но, возможно, все же поможет: в файлах elf-object обычно есть 2 таблицы символов: .symtab и .dynsym. nm по умолчанию читает .symtab, используйте nm -D для чтения таблицы .dynsym. dladdr (а также динамический загрузчик) используют таблицу .dynsym. таблица .symtab более полная. вы также можете заставить все символы находиться в таблице .dynsym, используя флаг -rdynamic компоновщика. однако это значительно замедляет связывание (например, в моем текущем проекте примерно на 200 мс). (примечание: сказанное выше относится к linux, но обработка символов работает в принципе одинаково на sunos. Параметры командной строки могут отличаться)

person frank    schedule 15.09.2011
comment
Спасибо за ответ. Я надеюсь, что у меня будет время поиграться с форматом ELF-файла в ближайшие несколько недель. - person evolvah; 23.09.2011