writing/2026-08-13/reverse engineering · radare2 · c++
r1 — symbol spoofing
abstract
обман дизассемблера через подмену имени символа. password-checker, который radare2 именует как std::operator==. разбор того, что реально обмануто, а что нет.
эксперимент из серии отчётов R1: взять обычную функцию-проверку пароля, переименовать её в mangled-имя std::operator== из libstdc++ и посмотреть, что reverse-инструменты покажут аналитику. окружение: debian 12 (x86-64), gcc 14.2.0, binutils 2.44, radare2 5.9.8 (без root). все poc-исходники и команды сборки — в этом же файле ниже.
tl;dr
- техника работает, но обманут не «дизассемблер» и не анализ — обманут слой именования (naming layer): radare2 деманглит строку из
.symtab/.dynsymи верит ей как метаданным. - функция, реально являющаяся password-checker’ом, отображается в r2 как
method.bool_std::operator<char...>(...)— идентично тому, как отображается настоящийstd::operator==в честном бинарнике (имена совпадают байт-в-байт). - при этом типы аргументов, возвращаемое значение, вызовы, call graph и pseudo-c код не пострадали: r2 не выводит типы из демангл-сигнатуры. в
pdcпараметры —int rdi, int rsi, выведенные из дизассемблера, а поддельная сигнатура появляется только в комментарии. - «обман» в 100% случаев — это spoofing метаданных (имя символа), а не подмена семантики анализа. инструменты, использующие сигнатуры по байтам (FLIRT, ghidra function id), этой техникой не обманываются.
- техника полностью снимается
strip’ом, если символ не экспортирован; переживаетstripтолько в.dynsym(shared library или-rdynamic), и тогда её видно вreadelf -Wsкак экспорт с аномальным размером. - опытный реверсер замечает подмену за минуты: несоответствие размера функции, вызовы
strcmp/putsвместо vtable-логикиbasic_string, строки"CORRECT PASSWORD", dwarf.
экспериментальная база
собраны и проанализированы (radare2 5.9.8 + binutils):
| poc | приём | результат в r2 |
|---|---|---|
pocA | в исходнике функция названа mangled-именем _ZSteqIc... | gcc пере-мангалит имя в _Z74_ZSteq..., потому что компилятор не знает, что идентификатор уже заманлен. в r2 — имя функции-идентификатор, а не std::operator== |
pocB | обычная функция check_password, переименована через objcopy --redefine-sym | r2: method.bool_std::operator_char__... — воспроизведено. pdc: сигнатура подделки — только комментарий; параметры из дизассемблера |
pocB.spoofed + strip | полный strip | символ удалён (его нет в .dynsym) → r2: fcn.00001159. spoof полностью снят |
pocB.so.spoofed / .rdyn.spoofed | shared lib / -rdynamic | objcopy --redefine-sym не переименовывает .dynsym — только .symtab! после strip остаётся честное check_password |
pocE (+.rdyn, .strip) | ассемблер: символ .globl _ZSteq... с самого начала | в -rdynamic варианте имя живёт в .dynsym и переживает strip; r2 показывает std::operator== |
pocC2 | локальные определения strcmp/strlen/memcpy/malloc/free (forwarding через dlsym(RTLD_NEXT)) | r2: sym.strcmp и т.д. — локальные функции; ii не показывает импорты |
pocC | то же без forwarding | бинарь падает (SIGSEGV): glibc интерпозирует свои внутренние вызовы malloc на поддельные |
pocD | 4 сложных имени: std::max<int>, std::operator<<, basic_string::compare(...) const, std::operator== | все демангл-ятся корректно. ошибочное имя ...EE9compare... (без K) не деманглится ни c++filt, ни r2 |
pocF | .symver — экспорт strcmp@GLIBC_2.2.5 с кастомным телом | r2: sym.strcmp_GLIBC_2.2.5 (defined), sym.imp.strcmp остаётся отдельным импортом |
pocG | __attribute__((alias)): _ZSteq... как alias check_password | оба имени на одном адресе; r2 берёт method.-имя; в pdf видны оба комментария — палевная улика |
control_real | настоящий std::operator== из libstdc++ (weak, header) | r2 показывает то же самое имя, что и подделка. отличие — только размер (132 против 54 байта) и тело |
ключевой контрольный результат — имена совпадают, размер и тело — нет:
# настоящий std::operator== (control_real)
0x00008da6 5 132 method.bool_std::operator_char__...const_
# подделка (pocB.spoofed)
0x00001159 4 54 method.bool_std::operator_char__...const_
как это работает на уровне elf
elf symbol table
по gABI (§4.4 «Symbol Table»):
typedef struct {
Elf64_Word st_name; // индекс в .strtab — НИКАКОЙ семантики, просто строка
Elf64_Addr st_value;
Elf64_Xword st_size;
unsigned char st_info; // bind/type: STB_GLOBAL/STT_FUNC
unsigned char st_other; // visibility
Elf64_Half st_shndx; // секция
} Elf64_Sym;
st_name — это индекс в строковой таблице; формат файла не накладывает на имя никаких ограничений — это произвольная строка. инструменты, которые «деманглят» имя, применяют к этой строке внешний по отношению к ELF стандарт (Itanium C++ ABI §5.1).
следствия:
- символ в
.symtabможет называться как угодно; ELF-валидность не нарушается objcopy --redefine-symменяет имя только в.symtab— потому что.dynsymгенерируется линкером, а objcopy патчит строку в.strtabstripудаляет.symtabцеликом; остаются только динамические символы (экспорты)
c++ abi: имя = сериализованный тип
Itanium C++ ABI §5.1 «External Names»: mangled-имя кодирует полную сигнатуру функции. пример из эксперимента:
_ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_
= bool std::operator==<char, std::char_traits<char>, std::allocator<char>>(
std::__cxx11::basic_string<...> const&, std::__cxx11::basic_string<...> const&)
деманглер — это парсер этого кодирования. он принимает любую строку и пытается её распарсить; если строка валидна — выдаёт «красивую» сигнатуру. никакой сверки с байтами функции не существует в принципе: у ELF нет механизма проверить соответствие имени телу.
подмена mangled-имени — это подмена «типа» для любого инструмента, который доверяет имени. проблема не в конкретном баге инструмента, а в архитектуре доверия: имя в ELF непроверяемо, а деманглер обязан ему верить — это его контракт.
plT/got и импорты
если функция импортируется, имя приходит из .dynstr и тоже непроверяемо. определение локального символа с именем либсишной функции (pocC2) вызывает symbol interposition: динамический линкер резолвит вызовы strcmp из любого модуля в нашу локальную версию. работает и в обратную сторону: glibc внутри себя вызывает наши поддельные malloc/free/strlen — что и вызвало SIGSEGV в pocC.
символьные версии
.symver добавляет к имени версию (strcmp@GLIBC_2.2.5) — тоже просто строка. pocF показал: r2 честно различает sym.strcmp_GLIBC_2.2.5 (наше определение) и sym.imp.strcmp (настоящий импорт).
почему radare2 «поверил»
полная цепочка из исходников r2 5.9.8 (фрагменты — в приложении ниже):
- чтение символа.
libr/bin/format/elf/elf.c→ символы из.symtab/.dynsym; имя копируется из строковой таблицы без проверок. - деманглинг и split class::method.
libr/bin/mangling/cxx.c(r_bin_demangle_cxx): деманглит через форк libiberty; затем эвристика — ищет последний::перед первой(в демангл-строке, режет строку на «class» и «method», регистрирует черезr_bin_file_add_method. для подделки это даёт class =std::operator==<char, std::char_traits<char>, std::allocator— отсюда префиксmethod.. это не семантическая классификация «метод», а побочный эффект эвристики разбиения. - именование функций.
libr/core/canal.cget_function_name(): если на адресе есть символ, зарегистрированный как method — имя =method.<class>.<method>. иначе — флагsym.<...>изlibr/core/cbin.c(snInit). - отображение.
r_core_anal_fcn_nameдеманглитfcn->nameпри выводеafl/pdf/pdc; демангл-сигнатура печатается как комментарий. - анализ — не зависит от имени. bb/edge/xrefs считаются по байтам (
libr/anal/*). подтверждено эмпирически:afi→args: 0;pdc→(int rdi, int rsi); тип возврата изeax-анализа.
fig — naming chain in r2
.symtab ──> st_name ──> demangler ──> split :: ──> method.<class>.<method>
│
└──> комментарий-сигнатура в pdc/pdf
анализ (afl, pdc, xrefs) ── по байтам, имя не используетсяважно: для настоящего std::operator== r2 делает ровно то же самое — потому что имя — единственный вход деманглера. на уровне именования подделка и оригинал неразличимы в принципе.
что реально было обмануто
| слой | обманут? | доказательство |
|---|---|---|
| symbol resolver (чтение symtab) | да, тривиально | is/iS показывают поддельное имя |
| деманглер | да | r2/c++filt выдают std::operator==<...> для валидной строки |
| function naming / ui (afl, pdf, графы) | да | afl, axt, agc, pdf-заголовки |
| call graph / xrefs | частично | рёбра считаются по байтам (не обманут), но подписи рёбер показывают поддельное имя |
| типы аргументов | нет | args: 0; pdc: int rdi, int rsi — из дизассемблера |
| возвращаемый тип | нет | pdc: int, из eax |
| decompiler output (r2 pdc) | нет (кроме имени) | сигнатура — комментарий; параметры из анализа |
| library function identification (FLIRT/FSDB) | нет | r2 fc использует байтовые сигнатуры FSDB, не имена |
| типы на call-site | нет | callee-прототип не применяется к вызову |
вывод: обманут слой «имя-и-подпись», который потребляет строку из ELF. анализ байтов, контрольный поток, data-flow — не обмануты, потому что они по именам не работают.
сравнение инструментов
radare2 (проверено эмпирически, 5.9.8)
- именование: обманут (
method./sym.-имена, комментарии-сигнатуры) - анализ: не обманут (
args: 0, pdc-параметры из дизассемблера) -e bin.demangle=false— показывает сырое имя (спуфинг виден)fc(FSDB-сигнатуры) — байтовые, именами не ведётся- деманглер r2 — собственный форк; не все валидные имена деманглит (проверено на
9compare)
ghidra (по документации; эмпирика не выполнялась — нет JRE в окружении)
- имена применяются к функциям при импорте; деманглер c++ на борту
- function id (FID) — хэши по инструкциям, не имена; по документации FID не переименовывает функции, у которых уже есть имя из символов: поддельное имя останется, FID ничего не заподозрит
- декомпилятор: параметры/типы из анализа байтов; вероятный исход — имя обмануто, типы — нет
- dwarf-импорт с честным
check_password— улика
ida / hex-rays (по документации; эмпирика недоступна без лицензии)
- FLIRT: байтовые сигнатуры. цитата из документации hex-rays: «FLIRT matches byte-based patterns, not symbol names… Function names are metadata». поддельное имя не влияет на FLIRT-сопоставление
- именование: ida доверяет symtab и деманглит — подделка отобразится как
std::operator==<...> - типы: hex-rays применяет прототипы из FLIRT-сигнатур и локальных типов; автоматического применения типа из произвольного демангл-имени — нет. ожидаемый исход: имя обмануто, декомпиляция — нет
binary ninja (по документации)
- символы применяются при импорте, деманглинг встроен — имя обмануто
- анализ (LLIL/MLIL/HLIL) — по байтам; параметры и типы не из имени
- type libraries — по пользовательским сигнатурам, не по именам
binutils (objdump/readelf/nm) — проверено
- не обмануты, потому что не анализируют: печатают имя как строку.
objdump -d -Cпоказывает демангл-имя в метке — выглядит «по-настоящему», но это просто подстановка строки. именно эти инструменты помогают разоблачить спуфинг
gdb
- деманглит имена для display;
break _ZSteq...сработает на поддельную функцию (не проверялся — gdb отсутствует в окружении; поведение следует из binutils-конвенции)
prior art
прямого канонического prior art с этим названием не найдено — техника существует в виде фольклора, и есть близкие академические работы:
- RARE — «When Binaries Talk Back: Representation-Confusion Attacks on LLM-Assisted Reverse Engineering» (Igor Santos-Grueiro, arXiv:2607.12507, июль 2026). ближайший современный аналог: атакующий контролирует содержимое бинарника, и LLM-конвейеры RE продвигают «наблюдение» (например, имя символа) в ранг «доверенного анализа». техника из R1 — частный случай этого класса для классических инструментов.
- FLIRT / FSDB / ghidra FID — байтовые сигнатуры. они же являются и защитой: «FLIRT evasion» (модификация тела) — это другая техника; подделка имени FLIRT не обходит (она и не пытается).
- symbol interposition (LD_PRELOAD,
.symver-хаки) — смежная, хорошо документированная тема;pocC2/pocCдемонстрируют её побочные эффекты. - PDB/DWARF spoofing — известная практика, родственная: подделка debug-метаданных.
- malware-фольклор: выпуск бинарников «с символами, но с вводящими в заблуждение именами» — обычная практика без устоявшегося термина. ближайший термин — symbol spoofing / symbol name confusion.
если у техники и есть «настоящее имя» — это representation confusion / metadata spoofing, ближайшая публикация — RARE. прямых аналогов с таким же приёмом (подмена mangled-имени stdlib-функции для обмана демангл-слоя) не найдено.
ограничения техники
- strip убивает: в обычном исполняемом файле символ живёт только в
.symtab; для выживания нужно, чтобы имя попало в.dynsym(shared library /-rdynamic) — а это уже видимый экспорт - objcopy не трогает
.dynsym: переименование динамических символов требует ручной правки.dynstr/.dynsymили ассемблерного определения имени - компилятор пере-мангалит: писать готовое mangled-имя в исходнике c++ нельзя — gcc пере-закодирует его (
pocA). реалистичные пути: ассемблерный символ,--redefine-sym, alias, патч.strtab,.symver - имя должно быть валидным mangled-именем: ошибка в кодировке → деманглер молча показывает сырую строку (вариант
9compare) → spoof раскрыт - тело должно быть ABI-совместимо с сигнатурой: несоответствие числа/типа аргументов и возврата видно в любом декомпиляторе
- размер: r2/ida показывают размер функции из анализа; настоящий
std::operator==— 132 байта, подделка — 54. сильное несоответствие — красный флаг - побочные эффекты интерпозиции: определение символов с именами libc ломает рантайм (
pocC, SIGSEGV) - dwarf-конфликт:
-gоставляет честноеDW_AT_name: check_password— мгновенное разоблачение - lto: без экспорта функция инлайнится и символ исчезает вовсе
- FLIRT/FID: байтовые матчеры технику не поддерживают и не подтверждают
- статическая линковка: работает, но рядом видны настоящие
__strcmp_avx2/ifunc-резолверы glibc — аномально для «std::operator==»
как реверсер обнаруживает spoofing
readelf -Ws— сырое имя +st_size/st_info/st_shndx: аномальный размер, func в неожиданной секции- сравнение с эталоном: демангл-имя
std::operator==→ ожидаемое тело — vtable-логикаbasic_string(data()/size()/memcmp), 100+ байт. вместо этого —strcmp/puts/строки - строки:
rabin2 -zz/stringsпоказывают"CORRECT PASSWORD","INVALID PASSWORD","s3cr3t-pass"в.rodataрядом с функцией - dwarf:
readelf --debug-dump=info— честное имя - strip: если символ не в
.dynsym— подделка исчезает; если в.dynsym— виден как подозрительный экспорт mangled-имени bin.demangle=falseв r2 — сырое имя- два имени на одном адресе (alias-вариант,
pocG) — оба комментария вpdf - нормализация через
nm -C: сигнатура + сравнение с декомпиляцией вскрывает расхождение (signature vs body mismatch) fc/FSDB, FLIRT, FID: матчеры не находят соответствия — «std::operator==» с байтами, не похожими на libstdc++, получает статус unknown → сигнал
практически: минуты, а не часы. техника не переживает контакта с аналитиком, который хотя бы раз видел настоящий libstdc++.
практическая ценность
- реальная ценность — низкая–средняя, и только в тайминге: приём «называй кастомную функцию как stdlib» работает против невнимательного анализа и против первого прохода автоинструментов, которые группируют «известные» функции
- защита от машинного анализа — слабая: FLIRT/FID/FSDB и ML-классификаторы опираются на байты; имена — лишь одна из фич. (LLM-ассистенты — да, уязвимы: это и есть предмет RARE-бумаги)
- сочетание с другими техниками повышает эффективность:
- реалистичное тело — настоящий копипаст
std::operator==из libstdc++ со встроенной проверкой: размер и структура правдоподобны, «секретная» логика спрятана в знакомом коде - настоящие символы вокруг (подлинные
sym.imp.strcmpи т.д.), чтобы symtab выглядел обычно - переживающий strip вариант:
-rdynamic/shared — экспорт в.dynsym - убрать строки (шифрование убирает
"CORRECT PASSWORD"— главную улику) и убрать dwarf
- реалистичное тело — настоящий копипаст
- для обмана человека работает через психологию: «это же просто
operator==» — скип. поэтому это «спуфинг метаданных», а не «обман анализа»
следующиe эксперименты r2–r7
- r2 — реалистичное тело: настоящий код
std::operator==из libstdc++ (~130 байт) с внедрённой скрытой проверкой; измерить, сколько времени уходит на функцию, которая выглядит как stdlib - r3 — dynsym-спуфинг без -rdynamic: вручную пропатчить
.dynstr/.dynsym, чтобы подделка переживалаstrip; проверитьreadelf --version-infoиdlopen/dlsym - r4 — меж-инструментальная сверка: один бинарник через r2/ghidra/ida, матрица имя/размер/прототип/декомпиляция
- r5 — FLIRT-коллизия: функция, байт-в-байт идентичная экспорту libc, но с другим именем — переименует ли инструмент её в настоящее libc-имя (что сильнее: имя или байты)
- r6 — msvc-мангинг: те же идеи на
?operator==@в PE/COFF - r7 — llm-конвейеры: прогнать spoofed-бинарник через LLM-assisted RE-пайплайны и измерить, сколько раз модель принимает поддельное имя за истину
вывод
- эксперимент воспроизведён и подтверждён: r2 именует password-checker как
std::operator==<char,...>— точно так же, как настоящий. но это обман слоя именования, а не анализа. - причина — архитектурное доверие: ELF-имя — произвольная строка, а деманглер обязан её распарсить и показать. проверки соответствия имени телу не существует ни в одном инструменте.
- ни один слой байтового анализа не обманут: аргументы, типы, возврат, декомпиляция, FLIRT/FID — всё работает по коду. ответ на вопрос «дизассемблер неправильно понял семантику или показал ложное имя»: только второе.
- техника снимается
strip, ломается невалидным mangling’ом, вскрывается размером/строками/dwarf и декомпиляцией. ценность — тайминг и психология, не криптографическая стойкость. - наиболее перспективные продолжения — r2 (реалистичное тело), r3 (dynsym-переживание strip), r5 (FLIRT-коллизия), r7 (LLM-ассистенты, где доверие к именам реально опасно — см. RARE).
источники
- ELF gABI §4.4 Symbol Table: https://www.sco.com/developers/gabi/latest/ch4.symtab.html
- Itanium C++ ABI, §5.1 External Names (mangling): https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
- Hex-Rays FLIRT: https://docs.hex-rays.com/core/flirt.md
- Ghidra Function ID: https://github.com/NationalSecurityAgency/ghidra
- RARE, arXiv:2607.12507: https://arxiv.org/abs/2607.12507
- исходники radare2 5.9.8:
libr/bin/mangling/cxx.c,libr/bin/bfile.c:1129,libr/core/canal.c,libr/core/cbin.c,libr/bin/demangle.c
приложение a — ключевые poc
pocB — objcopy-переименование (основной вариант)
// password checker compiled normally, then symbol renamed at ELF level
// via objcopy --redefine-sym to the real mangled name of
// std::operator==<char, std::char_traits<char>, std::allocator<char>>
#include <cstring>
#include <cstdio>
extern "C" bool check_password(const char *a, const char *b)
{
int r = strcmp(a, b);
if (r == 0) {
printf("CORRECT PASSWORD\n");
return true;
}
printf("INVALID PASSWORD\n");
return false;
}
int main(int argc, char **argv) {
const char *secret = "s3cr3t-pass";
if (argc < 2) {
printf("usage: %s <password>\n", argv[0]);
return 1;
}
bool ok = check_password(secret, argv[1]);
return ok ? 0 : 1;
}
pocE — ассемблерный символ (переживает strip при -rdynamic)
# assembly definition of the spoofed symbol: bool(const char*, const char*)
.text
.globl _ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_
.type _ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_, @function
_ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_:
push %rbp
mov %rsp, %rbp
sub $8, %rsp
lea str_secret(%rip), %rdi
call strcmp@PLT
test %eax, %eax
je .Lcorrect
lea str_invalid(%rip), %rdi
call puts@PLT
mov $0, %eax
jmp .Ldone
.Lcorrect:
lea str_correct(%rip), %rdi
call puts@PLT
mov $1, %eax
.Ldone:
leave
ret
.size _ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_, .-_ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_
.section .rodata
str_secret:
.string "s3cr3t-pass"
str_correct:
.string "CORRECT PASSWORD"
str_invalid:
.string "INVALID PASSWORD"
.section .note.GNU-stack,"",@progbits
контрольный пример — настоящий std::operator==
#include <string>
#include <cstdio>
int main(int argc, char **argv) {
std::string a = "aaa", b = argc > 1 ? argv[1] : "bbb";
bool eq = (a == b);
printf("eq=%d\n", (int)eq);
return 0;
}
приложение б — сборка и анализ
# poc B — objcopy --redefine-sym (работает для .symtab)
g++ -O1 -o bins/pocB src/pocB.cpp
objcopy --redefine-sym check_password=_ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EESA_ bins/pocB bins/pocB.spoofed
# poc C/C2 — локальные определения с именами libc (interposition)
g++ -O0 -o bins/pocC src/pocC.cpp # падает: glibc интерпозирует malloc
g++ -O0 -ldl -o bins/pocC2 src/pocC2.cpp # рабочий вариант с dlsym(RTLD_NEXT)
# poc E — ассемблерный символ; имя переживает strip при -rdynamic
g++ -O1 -rdynamic -o bins/pocE.rdyn src/pocE_main.cpp src/pocE_asm.S
strip bins/pocE.rdyn # имя остаётся в .dynsym
# poc F — symbol versioning (.symver); poc G — alias
g++ -O0 -o bins/pocF src/pocF.cpp
g++ -O1 -o bins/pocG src/pocG.cpp
# control — настоящий std::operator== из libstdc++
g++ -O0 -fkeep-inline-functions -o bins/control_real src/control_real.cpp
# анализ
r2 -q -e scr.color=false -c 'aa; afl' bins/pocB.spoofed
r2 -q -e scr.color=false -c 'aa; s 0x1159; pdc' bins/pocB.spoofed
r2 -q -e scr.color=false -c 'aa; s 0x1159; afi' bins/pocB.spoofed
readelf -Ws bins/pocE.rdyn.strip
objdump -d -C bins/pocE.rdyn
приложение в — фрагмент r2-исходников: split class::method
ключевая эвристика деманглера r2 (libr/bin/mangling/cxx.c, r_bin_demangle_cxx) — ищет последний :: перед первой ( и режет строку:
char *out = cplus_demangle_v3 (p, flags);
if (out) {
char *sign = (char *)strchr (out, '(');
if (sign) {
char *str = out;
char *ptr = NULL;
char *nerd = NULL;
for (;;) {
ptr = strstr (str, "::");
if (!ptr || ptr > sign) break;
nerd = ptr;
str = ptr + 1;
}
if (R_STR_ISNOTEMPTY (nerd)) {
*nerd = 0;
if (bf) {
RBinSymbol *sym = r_bin_file_add_method (bf, out, nerd + 2, 0);
/* ... */
}
*nerd = ':';
}
}
}
return out;
а get_function_name (libr/core/canal.c) превращает это в имя функции:
RBinSymbol *sym = ht_up_find (bf->bo->addr2klassmethod, addr, NULL);
if (sym && sym->classname && sym->name) {
return r_str_newf ("method.%s.%s", sym->classname, sym_name);
}