librz/bin: add STABS format parser (#6511)
Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
This commit is contained in:
parent
c0945b0231
commit
62bcb8f6fc
9 changed files with 1507 additions and 0 deletions
|
|
@ -103,6 +103,7 @@ rz_bin_sources = [
|
|||
'filter.c',
|
||||
'golang.c',
|
||||
'relocs_patch.c',
|
||||
'stabs.c',
|
||||
'p/bin_any.c',
|
||||
'p/bin_art.c',
|
||||
'p/bin_avr.c',
|
||||
|
|
|
|||
924
librz/bin/stabs.c
Normal file
924
librz/bin/stabs.c
Normal file
|
|
@ -0,0 +1,924 @@
|
|||
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_bin.h>
|
||||
#include <rz_bin_stabs.h>
|
||||
#include <rz_endian.h>
|
||||
#include <rz_type.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
/**
|
||||
* \file stabs.c
|
||||
* \brief Parser for the legacy STABS debug information format.
|
||||
*
|
||||
* The `.stab` section is an array of fixed-size 12-byte records. Each record is
|
||||
* laid out (little- or big-endian, matching the host object) as:
|
||||
*
|
||||
* | n_strx (4) | n_type (1) | n_other (1) | n_desc (2) | n_value (4) |
|
||||
*
|
||||
* The strings referenced by `n_strx` live in the `.stabstr` section. STABS files
|
||||
* can be the concatenation of several compilation units; the first record of
|
||||
* each unit is an `N_UNDF` header whose `n_value` is the size of that unit's
|
||||
* slice of the string table, so string offsets must be rebased per unit.
|
||||
*/
|
||||
|
||||
static bool stabs_big_endian(RzBinFile *bf) {
|
||||
return bf->o && bf->o->info ? bf->o->info->big_endian : false;
|
||||
}
|
||||
|
||||
static RzBinSection *stabs_section(RzBinFile *bf, const char *name) {
|
||||
rz_return_val_if_fail(bf && name, NULL);
|
||||
RzBinObject *o = bf->o;
|
||||
if (!o || !o->sections) {
|
||||
return NULL;
|
||||
}
|
||||
void **it;
|
||||
rz_pvector_foreach (o->sections, it) {
|
||||
RzBinSection *s = *it;
|
||||
if (s && RZ_STR_EQ(s->name, name)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// some toolchains prefix the section names, fall back to a suffix match
|
||||
rz_pvector_foreach (o->sections, it) {
|
||||
RzBinSection *s = *it;
|
||||
if (s && RZ_STR_ISNOTEMPTY(s->name) && rz_str_endswith(s->name, name)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ut8 *stabs_read_section(RzBinFile *bf, RzBinSection *section, ut64 *out_len) {
|
||||
if (!section || section->paddr >= bf->size) {
|
||||
return NULL;
|
||||
}
|
||||
ut64 len = RZ_MIN(section->size, bf->size - section->paddr);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
}
|
||||
// Allocate one extra byte and keep it zeroed so the buffer is always safe to
|
||||
// treat as a NUL-terminated string (the `.stabstr` table is not guaranteed
|
||||
// to end with a NUL on malformed input).
|
||||
ut8 *buf = calloc(len + 1, 1);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
}
|
||||
if (rz_buf_read_at(bf->buf, section->paddr, buf, len) != (st64)len) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
*out_len = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse the STABS information contained in a binary
|
||||
*
|
||||
* Reads the `.stab` and `.stabstr` sections, decodes every fixed-size record and
|
||||
* resolves its string (rebasing the per-compilation-unit string offsets).
|
||||
*
|
||||
* \param bf The binary file to read the STABS sections from
|
||||
* \return A newly allocated \ref RzBinStabs (owned by the caller), or NULL if the
|
||||
* binary has no usable STABS data
|
||||
*/
|
||||
RZ_API RZ_OWN RzBinStabs *rz_bin_stabs_parse(RZ_NONNULL RzBinFile *bf) {
|
||||
rz_return_val_if_fail(bf, NULL);
|
||||
RzBinSection *stab_sec = stabs_section(bf, ".stab");
|
||||
RzBinSection *stabstr_sec = stabs_section(bf, ".stabstr");
|
||||
if (!stab_sec || !stabstr_sec) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut64 stab_len = 0;
|
||||
ut8 *stab_buf = stabs_read_section(bf, stab_sec, &stab_len);
|
||||
if (!stab_buf) {
|
||||
return NULL;
|
||||
}
|
||||
if (stab_len < RZ_BIN_STABS_RECORD_SIZE) {
|
||||
free(stab_buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RzBinStabs *stabs = RZ_NEW0(RzBinStabs);
|
||||
if (!stabs) {
|
||||
free(stab_buf);
|
||||
return NULL;
|
||||
}
|
||||
stabs->big_endian = stabs_big_endian(bf);
|
||||
stabs->str = (char *)stabs_read_section(bf, stabstr_sec, &stabs->str_size);
|
||||
rz_vector_init(&stabs->entries, sizeof(RzBinStabsEntry), NULL, NULL);
|
||||
|
||||
const bool be = stabs->big_endian;
|
||||
// .stab must contain a whole number of fixed-size records; a trailing
|
||||
// partial record (corrupted input) is ignored.
|
||||
if (stab_len % RZ_BIN_STABS_RECORD_SIZE) {
|
||||
RZ_LOG_DEBUG("stabs: .stab size %" PFMT64u " is not a multiple of %d, ignoring trailing bytes\n",
|
||||
stab_len, RZ_BIN_STABS_RECORD_SIZE);
|
||||
}
|
||||
const size_t count = stab_len / RZ_BIN_STABS_RECORD_SIZE;
|
||||
// stab_len is already bounded by the file size (see stabs_read_section), so
|
||||
// only cap the up-front reservation to avoid a large allocation from a
|
||||
// crafted section size; the vector still grows to hold every record.
|
||||
rz_vector_reserve(&stabs->entries, RZ_MIN(count, 0x1000));
|
||||
|
||||
// Base offset into the string table for the current compilation unit. STABS
|
||||
// strings are split into per-unit slices: each unit opens with an N_UNDF
|
||||
// header record whose `value` field holds the byte size of that unit's
|
||||
// slice, so the next unit's strings start right after it.
|
||||
ut64 str_base = 0;
|
||||
ut64 unit_str_size = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const ut8 *p = stab_buf + i * RZ_BIN_STABS_RECORD_SIZE;
|
||||
RzBinStabsEntry e = { 0 };
|
||||
e.strx = rz_read_at_ble32(p, 0, be);
|
||||
e.type = p[4];
|
||||
e.other = p[5];
|
||||
e.desc = rz_read_at_ble16(p, 6, be);
|
||||
e.value = rz_read_at_ble32(p, 8, be);
|
||||
if (e.type == RZ_BIN_STABS_N_UNDF) {
|
||||
// new compilation unit: advance the base past the previous unit's
|
||||
// slice and remember the size of this one (from its header value)
|
||||
str_base += unit_str_size;
|
||||
unit_str_size = e.value;
|
||||
}
|
||||
ut64 off = str_base + e.strx;
|
||||
e.string = (e.strx && stabs->str && off < stabs->str_size)
|
||||
? stabs->str + off
|
||||
: NULL;
|
||||
rz_vector_push(&stabs->entries, &e);
|
||||
}
|
||||
free(stab_buf);
|
||||
return stabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Free a \ref RzBinStabs and everything it owns
|
||||
*
|
||||
* \param stabs The object to free, may be NULL
|
||||
*/
|
||||
RZ_API void rz_bin_stabs_free(RZ_NULLABLE RzBinStabs *stabs) {
|
||||
if (!stabs) {
|
||||
return;
|
||||
}
|
||||
rz_vector_fini(&stabs->entries);
|
||||
free(stabs->str);
|
||||
free(stabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Build source line information from parsed STABS data
|
||||
*
|
||||
* Only the records relevant to line information are used: N_SO selects the
|
||||
* current source file, N_FUN gives the base address of the current function and
|
||||
* N_SLINE carries a line number together with an offset relative to that
|
||||
* function. The result can be merged into RzBinObject.lines just like the
|
||||
* DWARF line program output.
|
||||
*
|
||||
* \param stabs The parsed STABS data
|
||||
* \return A newly allocated RzBinSourceLineInfo owned by the caller, or NULL on
|
||||
* allocation failure
|
||||
*/
|
||||
RZ_API RZ_OWN RzBinSourceLineInfo *rz_bin_stabs_source_line_info(RZ_NONNULL const RzBinStabs *stabs) {
|
||||
rz_return_val_if_fail(stabs, NULL);
|
||||
RzBinSourceLineInfoBuilder builder;
|
||||
rz_bin_source_line_info_builder_init(&builder);
|
||||
|
||||
char *comp_dir = NULL; // directory carried by a trailing-'/' N_SO
|
||||
char *cur_file = NULL; // full path of the current source file
|
||||
ut64 func_base = 0; // address of the current N_FUN
|
||||
bool have_func = false;
|
||||
|
||||
RzBinStabsEntry *e;
|
||||
rz_vector_foreach (&stabs->entries, e) {
|
||||
switch (e->type) {
|
||||
case RZ_BIN_STABS_N_SO:
|
||||
if (RZ_STR_ISEMPTY(e->string)) {
|
||||
// closing N_SO: the value is the first address no longer
|
||||
// covered, emit a closing sample like DW_LNE_end_sequence
|
||||
rz_bin_source_line_info_builder_push_sample(&builder, e->value, 0, 0, NULL);
|
||||
RZ_FREE(cur_file);
|
||||
RZ_FREE(comp_dir);
|
||||
have_func = false;
|
||||
func_base = 0;
|
||||
} else if (rz_str_endswith(e->string, "/")) {
|
||||
free(comp_dir);
|
||||
comp_dir = rz_str_dup(e->string);
|
||||
} else {
|
||||
free(cur_file);
|
||||
cur_file = comp_dir
|
||||
? rz_str_newf("%s%s", comp_dir, e->string)
|
||||
: rz_str_dup(e->string);
|
||||
}
|
||||
break;
|
||||
case RZ_BIN_STABS_N_FUN:
|
||||
if (RZ_STR_ISNOTEMPTY(e->string)) {
|
||||
// a function definition: subsequent N_SLINE offsets are
|
||||
// relative to this address
|
||||
func_base = e->value;
|
||||
have_func = true;
|
||||
}
|
||||
break;
|
||||
case RZ_BIN_STABS_N_SLINE: {
|
||||
ut64 addr = have_func ? func_base + e->value : e->value;
|
||||
rz_bin_source_line_info_builder_push_sample(&builder, addr, e->desc, 0, cur_file);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(cur_file);
|
||||
free(comp_dir);
|
||||
return rz_bin_source_line_info_builder_build_and_fini(&builder);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Symbol, type and variable extraction *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* The higher level information lives in the record strings, encoded with the
|
||||
* STABS type-descriptor grammar. It is a recursive (context-free) grammar:
|
||||
* aggregates nest arbitrarily (a struct member may be a pointer to an array of
|
||||
* another struct, ...), so it is handled with a recursive-descent parser, the
|
||||
* same approach rizin already uses for its DWARF and C/C++ type parsers. A
|
||||
* rough sketch of the grammar:
|
||||
*
|
||||
* type := typenum [ '=' typedef ]
|
||||
* typenum := '(' int ',' int ')' | int
|
||||
* typedef := range | pointer | array | struct | union | enum | xref | func | type
|
||||
* range := 'r' typenum ';' int ';' int ';'
|
||||
* pointer := '*' type
|
||||
* array := 'ar' type ';' int ';' int ';' type
|
||||
* struct := 's' size { member } ';' ( union := 'u' size { member } ';' )
|
||||
* member := name ':' type ',' bitoff ',' bitsize ';'
|
||||
* enum := 'e' { name ':' int ',' } ';'
|
||||
* xref := 'x' ('s'|'u'|'e') name ':'
|
||||
* func := 'f' type
|
||||
*
|
||||
* See https://sourceware.org/gdb/onlinedocs/stabs.html for the full grammar.
|
||||
*/
|
||||
|
||||
/// Encode a STABS type number (file, number) into a single key.
|
||||
#define STABS_TYPE_KEY(file, num) (((ut64)(ut32)(file) << 32) | (ut32)(num))
|
||||
|
||||
/// Upper bound on the type-descriptor recursion, to reject pathologically
|
||||
/// nested types from a crafted binary without overflowing the stack.
|
||||
#define STABS_MAX_TYPE_DEPTH 64
|
||||
|
||||
typedef struct {
|
||||
RzTypeDB *typedb;
|
||||
HtUP /*<ut64, RzType *>*/ *types; ///< canonical RzType per STABS type id, owned by the table
|
||||
ut32 anon_counter; ///< used to name untagged structs, unions and enums
|
||||
ut32 depth; ///< current type-descriptor recursion depth
|
||||
} StabsTypeParser;
|
||||
|
||||
static RzType *stabs_type_parse(StabsTypeParser *tp, const char **pp);
|
||||
|
||||
static RzType *stabs_ident(const char *name, RzTypeIdentifierKind kind) {
|
||||
RzType *t = RZ_NEW0(RzType);
|
||||
if (!t) {
|
||||
return NULL;
|
||||
}
|
||||
t->kind = RZ_TYPE_KIND_IDENTIFIER;
|
||||
t->identifier.kind = kind;
|
||||
t->identifier.name = rz_str_dup(name);
|
||||
t->identifier.is_const = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// Parse a type number "(file,num)" or a bare "num", advancing \p pp.
|
||||
static bool stabs_typenum(const char **pp, ut64 *key) {
|
||||
const char *p = *pp;
|
||||
if (*p == '(') {
|
||||
p++;
|
||||
char *end = NULL;
|
||||
long file = strtol(p, &end, 10);
|
||||
if (end == p || *end != ',') {
|
||||
return false;
|
||||
}
|
||||
p = end + 1;
|
||||
long num = strtol(p, &end, 10);
|
||||
if (end == p || *end != ')') {
|
||||
return false;
|
||||
}
|
||||
*key = STABS_TYPE_KEY(file, num);
|
||||
*pp = end + 1;
|
||||
return true;
|
||||
}
|
||||
if (IS_DIGIT(*p)) {
|
||||
char *end = NULL;
|
||||
long num = strtol(p, &end, 10);
|
||||
if (end == p) {
|
||||
return false;
|
||||
}
|
||||
*key = STABS_TYPE_KEY(0, num);
|
||||
*pp = end;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Parse a range descriptor "r<typeref>;lo;hi;" (already past 'r') and derive
|
||||
/// the size, signedness and whether it is a floating point type.
|
||||
static void stabs_parse_range(const char **pp, ut64 *size_bits, bool *is_signed, bool *is_float) {
|
||||
const char *p = *pp;
|
||||
ut64 dummy = 0;
|
||||
stabs_typenum(&p, &dummy);
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
const char *lo = p;
|
||||
bool lo_neg = (*p == '-');
|
||||
while (*p && *p != ';') {
|
||||
p++;
|
||||
}
|
||||
size_t lo_len = p - lo;
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
const char *hi = p;
|
||||
bool hi_neg = (*p == '-');
|
||||
while (*p && *p != ';') {
|
||||
p++;
|
||||
}
|
||||
size_t hi_len = p - hi;
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
*pp = p;
|
||||
|
||||
*is_float = false;
|
||||
*is_signed = lo_neg;
|
||||
// Floating point is encoded as "r(ref);bytes;0;".
|
||||
if (hi_len == 1 && hi[0] == '0' && !lo_neg && lo_len > 0 && !(lo_len == 1 && lo[0] == '0')) {
|
||||
*is_float = true;
|
||||
*is_signed = true;
|
||||
*size_bits = (ut64)strtoull(lo, NULL, 10) * 8;
|
||||
return;
|
||||
}
|
||||
// "0;-1" is GCC's unsigned with no fixed upper bound; treat as 4 byte unsigned.
|
||||
if (!lo_neg && lo_len == 1 && lo[0] == '0' && hi_neg) {
|
||||
*is_signed = false;
|
||||
*size_bits = 32;
|
||||
return;
|
||||
}
|
||||
ut64 hival = hi_neg ? 0 : (ut64)strtoull(hi, NULL, 10);
|
||||
ut64 bytes;
|
||||
if (lo_neg) {
|
||||
bytes = hival <= 0x7fULL ? 1 : hival <= 0x7fffULL ? 2
|
||||
: hival <= 0x7fffffffULL ? 4
|
||||
: 8;
|
||||
*is_signed = true;
|
||||
} else {
|
||||
bytes = hival <= 0xffULL ? 1 : hival <= 0xffffULL ? 2
|
||||
: hival <= 0xffffffffULL ? 4
|
||||
: 8;
|
||||
*is_signed = false;
|
||||
}
|
||||
*size_bits = bytes * 8;
|
||||
}
|
||||
|
||||
static const char *stabs_atomic_name(ut64 size_bits, bool is_signed, bool is_float) {
|
||||
if (is_float) {
|
||||
switch (size_bits) {
|
||||
case 32: return "float";
|
||||
case 64: return "double";
|
||||
default: return "long double";
|
||||
}
|
||||
}
|
||||
if (is_signed) {
|
||||
switch (size_bits) {
|
||||
case 8: return "char";
|
||||
case 16: return "short";
|
||||
case 32: return "int";
|
||||
default: return "long";
|
||||
}
|
||||
}
|
||||
switch (size_bits) {
|
||||
case 8: return "unsigned char";
|
||||
case 16: return "unsigned short";
|
||||
case 32: return "unsigned int";
|
||||
default: return "unsigned long";
|
||||
}
|
||||
}
|
||||
|
||||
/// Register an atomic base type (unless one with that name already exists) and
|
||||
/// return a fresh identifier referencing it.
|
||||
static RzType *stabs_atomic(StabsTypeParser *tp, const char *name, ut64 size_bits) {
|
||||
if (!rz_type_db_get_base_type(tp->typedb, name)) {
|
||||
RzBaseType *bt = rz_type_base_type_new(RZ_BASE_TYPE_KIND_ATOMIC);
|
||||
if (bt) {
|
||||
bt->name = rz_str_dup(name);
|
||||
bt->size = size_bits;
|
||||
bt->type = stabs_ident(name, RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
rz_type_db_save_base_type(tp->typedb, bt);
|
||||
}
|
||||
}
|
||||
return stabs_ident(name, RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
}
|
||||
|
||||
/// Register a typedef "name" aliasing \p target and return identifier(name).
|
||||
static RzType *stabs_make_typedef(StabsTypeParser *tp, const char *name, const RzType *target) {
|
||||
if (!rz_type_db_get_base_type(tp->typedb, name)) {
|
||||
RzBaseType *bt = rz_type_base_type_new(RZ_BASE_TYPE_KIND_TYPEDEF);
|
||||
if (bt) {
|
||||
bt->name = rz_str_dup(name);
|
||||
bt->type = target ? rz_type_clone(target) : stabs_ident("void", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
rz_type_db_save_base_type(tp->typedb, bt);
|
||||
}
|
||||
}
|
||||
return stabs_ident(name, RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
}
|
||||
|
||||
/// Parse a struct/union body "<size><member>...;", already past 's'/'u'.
|
||||
static RzType *stabs_struct_union(StabsTypeParser *tp, const char **pp, const char *name, bool is_struct) {
|
||||
const char *p = *pp;
|
||||
char *end = NULL;
|
||||
ut64 size_bytes = (ut64)strtoull(p, &end, 10);
|
||||
p = end;
|
||||
|
||||
char synth[64];
|
||||
if (!name) {
|
||||
rz_strf(synth, "anonymous %s %u", is_struct ? "struct" : "union", tp->anon_counter++);
|
||||
name = synth;
|
||||
}
|
||||
|
||||
RzTypeIdentifierKind ik = is_struct ? RZ_TYPE_IDENTIFIER_KIND_STRUCT : RZ_TYPE_IDENTIFIER_KIND_UNION;
|
||||
if (name && rz_type_db_get_base_type(tp->typedb, name)) {
|
||||
// already defined: skip the body and just reference it
|
||||
while (*p && *p != ';') {
|
||||
p++;
|
||||
}
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
*pp = p;
|
||||
return stabs_ident(name, ik);
|
||||
}
|
||||
|
||||
RzBaseType *bt = rz_type_base_type_new(is_struct ? RZ_BASE_TYPE_KIND_STRUCT : RZ_BASE_TYPE_KIND_UNION);
|
||||
if (!bt) {
|
||||
*pp = p;
|
||||
return NULL;
|
||||
}
|
||||
bt->name = rz_str_dup(name);
|
||||
bt->size = size_bytes * 8;
|
||||
|
||||
while (*p && *p != ';') {
|
||||
const char *ms = p;
|
||||
while (*p && *p != ':') {
|
||||
p++;
|
||||
}
|
||||
char *mname = rz_str_ndup(ms, p - ms);
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
}
|
||||
RzType *mtype = stabs_type_parse(tp, &p);
|
||||
ut64 bitoff = 0;
|
||||
if (*p == ',') {
|
||||
p++;
|
||||
bitoff = (ut64)strtoull(p, &end, 10);
|
||||
p = end;
|
||||
}
|
||||
if (*p == ',') {
|
||||
p++;
|
||||
(void)strtoull(p, &end, 10); // bit size, unused for non-bitfields
|
||||
p = end;
|
||||
}
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
if (is_struct) {
|
||||
RzTypeStructMember member = { .name = mname, .type = mtype, .offset = bitoff / 8, .size = 0 };
|
||||
rz_vector_push(&bt->struct_data.members, &member);
|
||||
} else {
|
||||
RzTypeUnionMember member = { .name = mname, .type = mtype, .offset = bitoff / 8, .size = 0 };
|
||||
rz_vector_push(&bt->union_data.members, &member);
|
||||
}
|
||||
}
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
*pp = p;
|
||||
|
||||
RzType *ident = stabs_ident(bt->name, ik);
|
||||
rz_type_db_save_base_type(tp->typedb, bt);
|
||||
return ident;
|
||||
}
|
||||
|
||||
/// Parse an enum body "name:val,...;", already past 'e'.
|
||||
static RzType *stabs_enum(StabsTypeParser *tp, const char **pp, const char *name) {
|
||||
const char *p = *pp;
|
||||
char synth[64];
|
||||
if (!name) {
|
||||
rz_strf(synth, "anonymous enum %u", tp->anon_counter++);
|
||||
name = synth;
|
||||
}
|
||||
if (name && rz_type_db_get_base_type(tp->typedb, name)) {
|
||||
while (*p && *p != ';') {
|
||||
p++;
|
||||
}
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
*pp = p;
|
||||
return stabs_ident(name, RZ_TYPE_IDENTIFIER_KIND_ENUM);
|
||||
}
|
||||
|
||||
RzBaseType *bt = rz_type_base_type_new(RZ_BASE_TYPE_KIND_ENUM);
|
||||
if (!bt) {
|
||||
*pp = p;
|
||||
return NULL;
|
||||
}
|
||||
bt->name = rz_str_dup(name);
|
||||
bt->size = 32;
|
||||
bt->type = stabs_ident("int", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
|
||||
char *end = NULL;
|
||||
while (*p && *p != ';') {
|
||||
const char *cs = p;
|
||||
while (*p && *p != ':') {
|
||||
p++;
|
||||
}
|
||||
char *cname = rz_str_ndup(cs, p - cs);
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
}
|
||||
st64 val = (st64)strtoll(p, &end, 10);
|
||||
p = end;
|
||||
if (*p == ',') {
|
||||
p++;
|
||||
}
|
||||
RzTypeEnumCase ec = { .name = cname, .val = val };
|
||||
rz_vector_push(&bt->enum_data.cases, &ec);
|
||||
}
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
*pp = p;
|
||||
|
||||
RzType *ident = stabs_ident(bt->name, RZ_TYPE_IDENTIFIER_KIND_ENUM);
|
||||
rz_type_db_save_base_type(tp->typedb, bt);
|
||||
return ident;
|
||||
}
|
||||
|
||||
/// Parse a type definition that follows '=' and register the canonical type
|
||||
/// for \p key. Returns an owned RzType.
|
||||
static RzType *stabs_type_def(StabsTypeParser *tp, const char **pp, ut64 key, const char *name) {
|
||||
const char *p = *pp;
|
||||
RzType *t = NULL;
|
||||
char d = *p;
|
||||
|
||||
if (d == '(' || d == '-' || IS_DIGIT(d)) {
|
||||
RzType *other = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
t = stabs_make_typedef(tp, name, other);
|
||||
rz_type_free(other);
|
||||
} else {
|
||||
t = other;
|
||||
}
|
||||
} else if (!d) {
|
||||
// The descriptor string ended prematurely (truncated or malformed
|
||||
// input): treat the missing type as void, without advancing past the
|
||||
// NUL terminator so the caller does not read out of bounds.
|
||||
t = stabs_ident("void", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
} else {
|
||||
p++; // consume the descriptor character
|
||||
switch (d) {
|
||||
case 'r': {
|
||||
ut64 sz = 32;
|
||||
bool sg = true, fl = false;
|
||||
stabs_parse_range(&p, &sz, &sg, &fl);
|
||||
t = stabs_atomic(tp, name ? name : stabs_atomic_name(sz, sg, fl), sz);
|
||||
break;
|
||||
}
|
||||
case '*': {
|
||||
RzType *inner = stabs_type_parse(tp, &p);
|
||||
t = RZ_NEW0(RzType);
|
||||
if (t) {
|
||||
t->kind = RZ_TYPE_KIND_POINTER;
|
||||
t->pointer.type = inner;
|
||||
t->pointer.is_const = false;
|
||||
} else {
|
||||
rz_type_free(inner);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
if (*p == 'r') {
|
||||
p++; // "ar"
|
||||
}
|
||||
RzType *idx = stabs_type_parse(tp, &p);
|
||||
rz_type_free(idx);
|
||||
char *end = NULL;
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
st64 lo = (st64)strtoll(p, &end, 10);
|
||||
p = end;
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
st64 hi = (st64)strtoll(p, &end, 10);
|
||||
p = end;
|
||||
if (*p == ';') {
|
||||
p++;
|
||||
}
|
||||
RzType *elem = stabs_type_parse(tp, &p);
|
||||
t = RZ_NEW0(RzType);
|
||||
if (t) {
|
||||
t->kind = RZ_TYPE_KIND_ARRAY;
|
||||
t->array.type = elem;
|
||||
t->array.count = (hi >= lo) ? (ut64)(hi - lo + 1) : 0;
|
||||
} else {
|
||||
rz_type_free(elem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
case 'u':
|
||||
t = stabs_struct_union(tp, &p, name, d == 's');
|
||||
break;
|
||||
case 'e':
|
||||
t = stabs_enum(tp, &p, name);
|
||||
break;
|
||||
case 'x': {
|
||||
char tag = *p;
|
||||
if (tag) {
|
||||
p++;
|
||||
}
|
||||
const char *xs = p;
|
||||
while (*p && *p != ':') {
|
||||
p++;
|
||||
}
|
||||
char *xname = rz_str_ndup(xs, p - xs);
|
||||
if (*p == ':') {
|
||||
p++;
|
||||
}
|
||||
RzTypeIdentifierKind ik = tag == 's' ? RZ_TYPE_IDENTIFIER_KIND_STRUCT
|
||||
: tag == 'u' ? RZ_TYPE_IDENTIFIER_KIND_UNION
|
||||
: tag == 'e' ? RZ_TYPE_IDENTIFIER_KIND_ENUM
|
||||
: RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED;
|
||||
t = stabs_ident(xname, ik);
|
||||
free(xname);
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
t = stabs_type_parse(tp, &p); // function returning type, approximated as the return type
|
||||
break;
|
||||
default:
|
||||
t = stabs_ident("void", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*pp = p;
|
||||
if (t && key) {
|
||||
ht_up_update(tp->types, key, rz_type_clone(t));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static RzType *stabs_type_parse(StabsTypeParser *tp, const char **pp) {
|
||||
if (tp->depth >= STABS_MAX_TYPE_DEPTH) {
|
||||
return stabs_ident("void", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
}
|
||||
tp->depth++;
|
||||
const char *p = *pp;
|
||||
ut64 key = 0;
|
||||
RzType *t = NULL;
|
||||
if (stabs_typenum(&p, &key)) {
|
||||
if (*p == '=') {
|
||||
p++;
|
||||
t = stabs_type_def(tp, &p, key, NULL);
|
||||
} else {
|
||||
RzType *canon = ht_up_find(tp->types, key, NULL);
|
||||
t = canon ? rz_type_clone(canon) : stabs_ident("void", RZ_TYPE_IDENTIFIER_KIND_UNSPECIFIED);
|
||||
}
|
||||
} else {
|
||||
// no type number: a descriptor follows directly
|
||||
t = stabs_type_def(tp, &p, 0, NULL);
|
||||
}
|
||||
*pp = p;
|
||||
tp->depth--;
|
||||
return t;
|
||||
}
|
||||
|
||||
static void stabs_symbol_free(RzBinStabsSymbol *sym) {
|
||||
if (!sym) {
|
||||
return;
|
||||
}
|
||||
free(sym->name);
|
||||
rz_type_free(sym->type);
|
||||
free(sym);
|
||||
}
|
||||
|
||||
static void stabs_add_symbol(RzBinStabsDebugInfo *di, const char *name, RzBinStabsSymbolKind kind, RZ_OWN RzType *type, ut64 value, ut64 function) {
|
||||
RzBinStabsSymbol *sym = RZ_NEW0(RzBinStabsSymbol);
|
||||
if (!sym) {
|
||||
rz_type_free(type);
|
||||
return;
|
||||
}
|
||||
sym->name = rz_str_dup(name);
|
||||
sym->kind = kind;
|
||||
sym->type = type;
|
||||
sym->value = value;
|
||||
sym->function = function;
|
||||
rz_pvector_push(&di->symbols, sym);
|
||||
}
|
||||
|
||||
static void stabs_finalize_callable(RzTypeDB *typedb, RzCallable **callable) {
|
||||
if (!*callable) {
|
||||
return;
|
||||
}
|
||||
if (!rz_type_func_save(typedb, *callable)) {
|
||||
rz_type_callable_free(*callable);
|
||||
}
|
||||
*callable = NULL;
|
||||
}
|
||||
|
||||
static void stabs_process_symbol(StabsTypeParser *tp, RzBinStabsDebugInfo *di, const RzBinStabsEntry *e, ut64 *cur_func, RzCallable **callable) {
|
||||
const char *s = e->string;
|
||||
const char *colon = strchr(s, ':');
|
||||
if (!colon) {
|
||||
return;
|
||||
}
|
||||
char *name = (colon == s) ? NULL : rz_str_ndup(s, colon - s);
|
||||
const char *p = colon + 1;
|
||||
char desc = *p;
|
||||
|
||||
// A missing descriptor letter (the type number starts right away) denotes a
|
||||
// local variable.
|
||||
if (desc == '(' || desc == '-' || IS_DIGIT(desc)) {
|
||||
RzType *t = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_LOCAL, t, e->value, *cur_func);
|
||||
} else {
|
||||
rz_type_free(t);
|
||||
}
|
||||
free(name);
|
||||
return;
|
||||
}
|
||||
|
||||
p++; // consume the descriptor letter
|
||||
switch (desc) {
|
||||
case 't': { // typedef or named atomic type
|
||||
ut64 key = 0;
|
||||
if (stabs_typenum(&p, &key)) {
|
||||
if (*p == '=') {
|
||||
p++;
|
||||
rz_type_free(stabs_type_def(tp, &p, key, name));
|
||||
} else {
|
||||
RzType *target = ht_up_find(tp->types, key, NULL);
|
||||
if (name && target) {
|
||||
rz_type_free(stabs_make_typedef(tp, name, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'T': { // struct/union/enum tag
|
||||
ut64 key = 0;
|
||||
if (stabs_typenum(&p, &key) && *p == '=') {
|
||||
p++;
|
||||
rz_type_free(stabs_type_def(tp, &p, key, name));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'G': { // global variable (address comes from the symbol table)
|
||||
RzType *t = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_GLOBAL, t, 0, 0);
|
||||
} else {
|
||||
rz_type_free(t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'S':
|
||||
case 'V': { // file or function scope static
|
||||
RzType *t = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_STATIC, t, e->value, 0);
|
||||
} else {
|
||||
rz_type_free(t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'F':
|
||||
case 'f': { // function
|
||||
stabs_finalize_callable(tp->typedb, callable);
|
||||
RzType *ret = stabs_type_parse(tp, &p);
|
||||
*cur_func = e->value;
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_FUNCTION, rz_type_clone(ret), e->value, 0);
|
||||
*callable = rz_type_callable_new(name);
|
||||
if (*callable) {
|
||||
(*callable)->ret = ret;
|
||||
ret = NULL;
|
||||
}
|
||||
}
|
||||
rz_type_free(ret);
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
case 'P': { // parameter
|
||||
RzType *t = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_PARAMETER, rz_type_clone(t), e->value, *cur_func);
|
||||
if (*callable) {
|
||||
RzCallableArg *arg = rz_type_callable_arg_new(tp->typedb, name, t);
|
||||
t = NULL;
|
||||
if (arg) {
|
||||
rz_type_callable_arg_add(*callable, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
rz_type_free(t);
|
||||
break;
|
||||
}
|
||||
case 'r': { // register variable
|
||||
RzType *t = stabs_type_parse(tp, &p);
|
||||
if (name) {
|
||||
stabs_add_symbol(di, name, RZ_BIN_STABS_SYMBOL_LOCAL, t, e->value, *cur_func);
|
||||
} else {
|
||||
rz_type_free(t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Recover symbols, types and variables from parsed STABS data
|
||||
*
|
||||
* Walks the records and decodes the STABS type-descriptor grammar. Types and
|
||||
* function prototypes are registered directly into \p typedb; the functions,
|
||||
* global/static variables, parameters and local variables are returned so the
|
||||
* caller can turn them into analysis objects.
|
||||
*
|
||||
* \param stabs The parsed STABS data
|
||||
* \param typedb The type database the recovered types and prototypes are added to
|
||||
* \return A newly allocated \ref RzBinStabsDebugInfo owned by the caller, or NULL
|
||||
* on allocation failure
|
||||
*/
|
||||
RZ_API RZ_OWN RzBinStabsDebugInfo *rz_bin_stabs_debug_info(RZ_NONNULL const RzBinStabs *stabs, RZ_NONNULL RzTypeDB *typedb) {
|
||||
rz_return_val_if_fail(stabs && typedb, NULL);
|
||||
RzBinStabsDebugInfo *di = RZ_NEW0(RzBinStabsDebugInfo);
|
||||
if (!di) {
|
||||
return NULL;
|
||||
}
|
||||
rz_pvector_init(&di->symbols, (RzPVectorFree)stabs_symbol_free);
|
||||
|
||||
StabsTypeParser tp = {
|
||||
.typedb = typedb,
|
||||
.types = ht_up_new(NULL, (HtUPFreeValue)rz_type_free)
|
||||
};
|
||||
if (!tp.types) {
|
||||
rz_bin_stabs_debug_info_free(di);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut64 cur_func = 0;
|
||||
RzCallable *callable = NULL;
|
||||
RzBinStabsEntry *e;
|
||||
rz_vector_foreach (&stabs->entries, e) {
|
||||
if (RZ_STR_ISEMPTY(e->string)) {
|
||||
continue;
|
||||
}
|
||||
switch (e->type) {
|
||||
case RZ_BIN_STABS_N_GSYM:
|
||||
case RZ_BIN_STABS_N_STSYM:
|
||||
case RZ_BIN_STABS_N_LCSYM:
|
||||
case RZ_BIN_STABS_N_FUN:
|
||||
case RZ_BIN_STABS_N_LSYM:
|
||||
case RZ_BIN_STABS_N_PSYM:
|
||||
case RZ_BIN_STABS_N_RSYM:
|
||||
stabs_process_symbol(&tp, di, e, &cur_func, &callable);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
stabs_finalize_callable(typedb, &callable);
|
||||
ht_up_free(tp.types);
|
||||
return di;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Free a \ref RzBinStabsDebugInfo and everything it owns
|
||||
*
|
||||
* \param di The object to free, may be NULL
|
||||
*/
|
||||
RZ_API void rz_bin_stabs_debug_info_free(RZ_NULLABLE RzBinStabsDebugInfo *di) {
|
||||
if (!di) {
|
||||
return;
|
||||
}
|
||||
rz_pvector_fini(&di->symbols);
|
||||
free(di);
|
||||
}
|
||||
|
|
@ -208,6 +208,7 @@ RZ_API bool rz_core_bin_apply_info(RzCore *r, RzBinFile *binfile, ut32 mask) {
|
|||
}
|
||||
if (mask & RZ_CORE_BIN_ACC_DWARF) {
|
||||
rz_core_bin_apply_dwarf(r, binfile);
|
||||
rz_core_bin_apply_stabs(r, binfile);
|
||||
}
|
||||
if (mask & RZ_CORE_BIN_ACC_LUAC_DEBUG) {
|
||||
rz_core_bin_apply_luac_debug(r, binfile);
|
||||
|
|
@ -697,6 +698,126 @@ RZ_API bool rz_core_bin_apply_dwarf(RzCore *core, RzBinFile *binfile) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse STABS debug information and apply its source line info
|
||||
*
|
||||
* STABS is the legacy debug format that predates DWARF and can still be found in
|
||||
* old binaries (and in objects produced by GCC <= 12 with -gstabs). Only the
|
||||
* source line information is consumed here; it is merged into the binary's line
|
||||
* info so that commands such as \p ix and source-aware disassembly work the same
|
||||
* way they do for DWARF.
|
||||
* Look up the virtual address of a named symbol in the binary's symbol table.
|
||||
* STABS global symbols carry no address of their own, so it is recovered here.
|
||||
*/
|
||||
static ut64 stabs_global_vaddr(RzBinFile *binfile, const char *name) {
|
||||
if (!binfile->o || !binfile->o->symbols) {
|
||||
return UT64_MAX;
|
||||
}
|
||||
void **it;
|
||||
rz_pvector_foreach (binfile->o->symbols, it) {
|
||||
RzBinSymbol *sym = *it;
|
||||
if (RZ_STR_EQ(sym->name, name)) {
|
||||
return sym->vaddr;
|
||||
}
|
||||
}
|
||||
return UT64_MAX;
|
||||
}
|
||||
|
||||
/// Turn a single recovered STABS variable into an analysis global variable.
|
||||
/// Returns true if a global variable was created.
|
||||
static bool stabs_apply_global(RzCore *core, RzBinFile *binfile, const RzBinStabsSymbol *sym) {
|
||||
if (!sym->type) {
|
||||
return false;
|
||||
}
|
||||
ut64 addr;
|
||||
if (sym->kind == RZ_BIN_STABS_SYMBOL_STATIC) {
|
||||
addr = sym->value;
|
||||
} else if (sym->kind == RZ_BIN_STABS_SYMBOL_GLOBAL) {
|
||||
// a STABS global carries no address of its own, recover it from the symbol table
|
||||
addr = stabs_global_vaddr(binfile, sym->name);
|
||||
if (addr == UT64_MAX) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return rz_analysis_var_global_create(core->analysis, sym->name, rz_type_clone(sym->type), addr);
|
||||
}
|
||||
|
||||
/// Merge the source line information recovered from STABS into the bin object.
|
||||
static bool stabs_apply_line_info(RzBinFile *binfile, RzBinStabs *stabs) {
|
||||
RzBinSourceLineInfo *li = rz_bin_stabs_source_line_info(stabs);
|
||||
if (!li) {
|
||||
return false;
|
||||
}
|
||||
bool applied = false;
|
||||
if (li->samples_count) {
|
||||
if (!binfile->o->lines) {
|
||||
binfile->o->lines = RZ_NEW0(RzBinSourceLineInfo);
|
||||
if (binfile->o->lines) {
|
||||
rz_str_constpool_init(&binfile->o->lines->filename_pool);
|
||||
}
|
||||
}
|
||||
if (binfile->o->lines) {
|
||||
rz_bin_source_line_info_merge(binfile->o->lines, li);
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
rz_bin_source_line_info_free(li);
|
||||
return applied;
|
||||
}
|
||||
|
||||
/// Recover types, function signatures and global variables from STABS. The types
|
||||
/// and the function prototypes are registered into the type database by the
|
||||
/// extractor; only the global and static variables are applied here.
|
||||
static bool stabs_apply_debug_info(RzCore *core, RzBinFile *binfile, RzBinStabs *stabs) {
|
||||
RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
|
||||
if (!typedb) {
|
||||
return false;
|
||||
}
|
||||
RzBinStabsDebugInfo *di = rz_bin_stabs_debug_info(stabs, typedb);
|
||||
if (!di) {
|
||||
return false;
|
||||
}
|
||||
bool applied = false;
|
||||
void **it;
|
||||
rz_pvector_foreach (&di->symbols, it) {
|
||||
if (stabs_apply_global(core, binfile, *it)) {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
rz_bin_stabs_debug_info_free(di);
|
||||
return applied;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Recover STABS debug information from a binary into the analysis
|
||||
*
|
||||
* Applies the source line information, registers the recovered types and
|
||||
* function prototypes into the type database and creates analysis global
|
||||
* variables for the recovered globals and statics.
|
||||
*
|
||||
* \param core The current core, whose analysis receives the recovered information
|
||||
* \param binfile The binary file to read the STABS sections from
|
||||
* \return true if any information was applied, false otherwise
|
||||
*/
|
||||
RZ_API bool rz_core_bin_apply_stabs(RzCore *core, RzBinFile *binfile) {
|
||||
rz_return_val_if_fail(core && core->analysis && binfile, false);
|
||||
if (!rz_config_get_bool(core->config, "bin.dbginfo") || !binfile->o) {
|
||||
return false;
|
||||
}
|
||||
RzBinStabs *stabs = rz_bin_stabs_parse(binfile);
|
||||
if (!stabs) {
|
||||
return false;
|
||||
}
|
||||
bool applied = stabs_apply_line_info(binfile, stabs);
|
||||
if (stabs_apply_debug_info(core, binfile, stabs)) {
|
||||
applied = true;
|
||||
}
|
||||
rz_bin_stabs_free(stabs);
|
||||
return applied;
|
||||
}
|
||||
|
||||
static inline bool is_initfini(RzBinAddr *entry) {
|
||||
switch (entry->type) {
|
||||
case RZ_BIN_ENTRY_TYPE_INIT:
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ typedef struct rz_bin_file_t RzBinFile;
|
|||
typedef struct rz_bin_reloc_storage_t RzBinRelocStorage;
|
||||
|
||||
#include <rz_bin_dwarf.h>
|
||||
#include <rz_bin_stabs.h>
|
||||
#include <rz_pdb.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
134
librz/include/rz_bin_stabs.h
Normal file
134
librz/include/rz_bin_stabs.h
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#ifndef RZ_BIN_STABS_H
|
||||
#define RZ_BIN_STABS_H
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_vector.h>
|
||||
#include <rz_type.h>
|
||||
#include <rz_bin_source_line.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \file rz_bin_stabs.h
|
||||
* \brief Parser for the STABS debugging information format.
|
||||
*
|
||||
* STABS (symbol table strings) is the legacy debug format emitted by old
|
||||
* toolchains (and by GCC up to version 12 with -gstabs). The information is
|
||||
* stored as an array of fixed 12-byte records in the `.stab` section, with the
|
||||
* strings kept in the separate `.stabstr` section.
|
||||
*
|
||||
* See https://sourceware.org/gdb/onlinedocs/stabs.html for the format details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief STABS symbol descriptor types (the on-disk `n_type` field).
|
||||
*
|
||||
* The values are fixed by the format.
|
||||
* See https://sourceware.org/gdb/onlinedocs/stabs.html for details.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_BIN_STABS_N_UNDF = 0x00, ///< Header record (and undefined entries)
|
||||
RZ_BIN_STABS_N_GSYM = 0x20, ///< Global symbol
|
||||
RZ_BIN_STABS_N_FNAME = 0x22, ///< Function name (BSD Fortran)
|
||||
RZ_BIN_STABS_N_FUN = 0x24, ///< Function
|
||||
RZ_BIN_STABS_N_STSYM = 0x26, ///< Static symbol (data segment)
|
||||
RZ_BIN_STABS_N_LCSYM = 0x28, ///< Static symbol (bss segment)
|
||||
RZ_BIN_STABS_N_MAIN = 0x2a, ///< Name of main routine
|
||||
RZ_BIN_STABS_N_OPT = 0x3c, ///< Compiler options marker
|
||||
RZ_BIN_STABS_N_RSYM = 0x40, ///< Register variable
|
||||
RZ_BIN_STABS_N_SLINE = 0x44, ///< Source line number in the text segment
|
||||
RZ_BIN_STABS_N_DSLINE = 0x46, ///< Source line number in the data segment
|
||||
RZ_BIN_STABS_N_BSLINE = 0x48, ///< Source line number in the bss segment
|
||||
RZ_BIN_STABS_N_SO = 0x64, ///< Source file name
|
||||
RZ_BIN_STABS_N_LSYM = 0x80, ///< Local symbol or type definition
|
||||
RZ_BIN_STABS_N_BINCL = 0x82, ///< Beginning of an include file
|
||||
RZ_BIN_STABS_N_SOL = 0x84, ///< Name of an included source file
|
||||
RZ_BIN_STABS_N_PSYM = 0xa0, ///< Function parameter
|
||||
RZ_BIN_STABS_N_EINCL = 0xa2, ///< End of an include file
|
||||
RZ_BIN_STABS_N_LBRAC = 0xc0, ///< Beginning of a lexical block (scope)
|
||||
RZ_BIN_STABS_N_EXCL = 0xc2, ///< Placeholder for a deleted include file
|
||||
RZ_BIN_STABS_N_RBRAC = 0xe0, ///< End of a lexical block (scope)
|
||||
} RzBinStabsType;
|
||||
|
||||
/** Size in bytes of a single on-disk STABS record. */
|
||||
#define RZ_BIN_STABS_RECORD_SIZE 12
|
||||
|
||||
/**
|
||||
* \brief A single decoded STABS record.
|
||||
*
|
||||
* On disk every record is a fixed 12-byte little/big-endian structure laid out
|
||||
* as `strx` (4 bytes), `type` (1 byte), `other` (1 byte), `desc` (2 bytes) and
|
||||
* `value` (4 bytes). The fields are independent; the values below are the
|
||||
* already decoded fields.
|
||||
*/
|
||||
typedef struct rz_bin_stabs_entry_t {
|
||||
ut32 strx; ///< Offset of the associated string inside the current unit's string table slice
|
||||
RzBinStabsType type; ///< Symbol descriptor type
|
||||
ut8 other; ///< Reserved field, usually zero
|
||||
ut16 desc; ///< Description field, e.g. the line number for N_SLINE records
|
||||
ut64 value; ///< An address, a function-relative offset or, for an N_UNDF header, the byte size of the unit's string table slice
|
||||
RZ_NULLABLE const char *string; ///< The resolved string, borrowed from RzBinStabs.str (may be NULL)
|
||||
} RzBinStabsEntry;
|
||||
|
||||
/**
|
||||
* \brief All STABS information parsed out of a binary.
|
||||
*/
|
||||
typedef struct rz_bin_stabs_t {
|
||||
RzVector /*<RzBinStabsEntry>*/ entries; ///< The decoded records, in file order
|
||||
char *str; ///< Copy of the `.stabstr` string table
|
||||
ut64 str_size; ///< Size of the string table in bytes
|
||||
bool big_endian; ///< Endianness used to decode the records
|
||||
} RzBinStabs;
|
||||
|
||||
/**
|
||||
* \brief The kind of program object a STABS symbol describes.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_BIN_STABS_SYMBOL_FUNCTION, ///< A function (descriptor 'F' or 'f')
|
||||
RZ_BIN_STABS_SYMBOL_GLOBAL, ///< A global variable (descriptor 'G')
|
||||
RZ_BIN_STABS_SYMBOL_STATIC, ///< A static variable (descriptor 'S'/'V', or N_LCSYM)
|
||||
RZ_BIN_STABS_SYMBOL_PARAMETER, ///< A function parameter (descriptor 'p'/'P')
|
||||
RZ_BIN_STABS_SYMBOL_LOCAL, ///< A function local variable
|
||||
} RzBinStabsSymbolKind;
|
||||
|
||||
/**
|
||||
* \brief A symbol (function, variable or parameter) recovered from STABS.
|
||||
*/
|
||||
typedef struct rz_bin_stabs_symbol_t {
|
||||
char *name; ///< Symbol name
|
||||
RzBinStabsSymbolKind kind; ///< What the symbol describes
|
||||
RZ_NULLABLE RzType *type; ///< Resolved type, owned by this symbol (may be NULL)
|
||||
ut64 value; ///< Absolute address for functions/globals/statics, frame offset for parameters/locals
|
||||
ut64 function; ///< For parameters and locals: address of the enclosing function
|
||||
} RzBinStabsSymbol;
|
||||
|
||||
/**
|
||||
* \brief All higher level debug information recovered from STABS.
|
||||
*
|
||||
* The types and the function signatures are registered directly into the
|
||||
* RzTypeDB passed to rz_bin_stabs_debug_info(). The remaining symbols
|
||||
* (functions, globals, statics, parameters and locals) are returned here so the
|
||||
* caller can turn them into analysis objects.
|
||||
*/
|
||||
typedef struct rz_bin_stabs_debug_info_t {
|
||||
RzPVector /*<RzBinStabsSymbol *>*/ symbols; ///< Recovered symbols
|
||||
} RzBinStabsDebugInfo;
|
||||
|
||||
struct rz_bin_file_t;
|
||||
|
||||
RZ_API RZ_OWN RzBinStabs *rz_bin_stabs_parse(RZ_NONNULL struct rz_bin_file_t *bf);
|
||||
RZ_API void rz_bin_stabs_free(RZ_NULLABLE RzBinStabs *stabs);
|
||||
RZ_API RZ_OWN RzBinSourceLineInfo *rz_bin_stabs_source_line_info(RZ_NONNULL const RzBinStabs *stabs);
|
||||
RZ_API RZ_OWN RzBinStabsDebugInfo *rz_bin_stabs_debug_info(RZ_NONNULL const RzBinStabs *stabs, RZ_NONNULL RzTypeDB *typedb);
|
||||
RZ_API void rz_bin_stabs_debug_info_free(RZ_NULLABLE RzBinStabsDebugInfo *di);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -1015,6 +1015,7 @@ RZ_API bool rz_core_bin_apply_config(RzCore *r, RzBinFile *binfile);
|
|||
RZ_API bool rz_core_bin_apply_maps(RzCore *core, RzBinFile *binfile, bool va);
|
||||
RZ_API bool rz_core_bin_apply_main(RzCore *r, RzBinFile *binfile, bool va);
|
||||
RZ_API bool rz_core_bin_apply_dwarf(RzCore *core, RzBinFile *binfile);
|
||||
RZ_API bool rz_core_bin_apply_stabs(RzCore *core, RzBinFile *binfile);
|
||||
RZ_API bool rz_core_bin_apply_entry(RzCore *core, RzBinFile *binfile, bool va);
|
||||
RZ_API bool rz_core_bin_apply_sections(RzCore *core, RzBinFile *binfile, bool va);
|
||||
RZ_API bool rz_core_bin_apply_relocs(RzCore *core, RzBinFile *binfile, bool va);
|
||||
|
|
|
|||
54
test/db/cmd/stabs
Normal file
54
test/db/cmd/stabs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
NAME=stabs ELF source line info
|
||||
FILE=bins/elf/stabs/stabs_hello
|
||||
CMDS=<<EOF
|
||||
ix
|
||||
ixj
|
||||
ixf
|
||||
s 0x401172
|
||||
ix.
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00401136 stabs_hello.c 6 0
|
||||
0x00401144 stabs_hello.c 7 0
|
||||
0x0040114f stabs_hello.c 8 0
|
||||
0x00401152 stabs_hello.c 9 0
|
||||
0x00401154 stabs_hello.c 11 0
|
||||
0x00401160 stabs_hello.c 12 0
|
||||
0x00401172 stabs_hello.c 13 0
|
||||
0x0040118b stabs_hello.c 14 0
|
||||
0x00401190 stabs_hello.c 15 0
|
||||
0x00401192 - 0 0
|
||||
[{"file":"stabs_hello.c","line":6,"addr":4198710},{"file":"stabs_hello.c","line":7,"addr":4198724},{"file":"stabs_hello.c","line":8,"addr":4198735},{"file":"stabs_hello.c","line":9,"addr":4198738},{"file":"stabs_hello.c","line":11,"addr":4198740},{"file":"stabs_hello.c","line":12,"addr":4198752},{"file":"stabs_hello.c","line":13,"addr":4198770},{"file":"stabs_hello.c","line":14,"addr":4198795},{"file":"stabs_hello.c","line":15,"addr":4198800},{"line":0,"addr":4198802}]
|
||||
[Source file]
|
||||
stabs_hello.c
|
||||
0x00401172 stabs_hello.c 13 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=stabs ELF symbol type and variable extraction
|
||||
FILE=bins/elf/stabs/stabs_syms
|
||||
CMDS=<<EOF
|
||||
t~point,color,uint_t
|
||||
ts point
|
||||
te color
|
||||
tf add
|
||||
tf scale
|
||||
avgl
|
||||
avglj
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
color
|
||||
point
|
||||
uint_t
|
||||
pf "d4d4 x y"
|
||||
RED = 0x0
|
||||
GREEN = 0x1
|
||||
BLUE = 0x2
|
||||
int add(int a, int b);
|
||||
long scale(struct point *p, unsigned int factor);
|
||||
global int g_counter @ 0x404018
|
||||
global struct point g_origin @ 0x404030
|
||||
global unsigned char [16] s_buffer @ 0x404040
|
||||
[{"name":"g_counter","type":"int","size":4,"addr":"0x404018"},{"name":"g_origin","type":"struct point","size":8,"addr":"0x404030"},{"name":"s_buffer","type":"unsigned char [16]","size":16,"addr":"0x404040"}]
|
||||
EOF
|
||||
RUN
|
||||
|
|
@ -27,6 +27,7 @@ if get_option('enable_tests') and cli_enabled
|
|||
'pdb',
|
||||
'project_migrate',
|
||||
'rzpipe',
|
||||
'stabs',
|
||||
'str_search',
|
||||
]
|
||||
|
||||
|
|
|
|||
270
test/integration/test_stabs.c
Normal file
270
test/integration/test_stabs.c
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// SPDX-FileCopyrightText: 2026 RizinOrg <info@rizin.re>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_util.h>
|
||||
#include <rz_bin.h>
|
||||
#include <rz_bin_stabs.h>
|
||||
#include <rz_type.h>
|
||||
#include "../unit/minunit.h"
|
||||
|
||||
static RzBinStabsEntry *entry_at(RzBinStabs *s, size_t i) {
|
||||
return (RzBinStabsEntry *)rz_vector_index_ptr(&s->entries, i);
|
||||
}
|
||||
|
||||
static RzBinStabsSymbol *find_sym(RzBinStabsDebugInfo *di, const char *name) {
|
||||
void **it;
|
||||
rz_pvector_foreach (&di->symbols, it) {
|
||||
RzBinStabsSymbol *s = *it;
|
||||
if (RZ_STR_EQ(s->name, name)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool test_stabs(void) {
|
||||
RzBin *bin = rz_bin_new();
|
||||
RzIO *io = rz_io_new();
|
||||
rz_io_bind(io, &bin->iob);
|
||||
RzBinOptions opt = { 0 };
|
||||
rz_bin_options_init(&opt, 0, 0, 0, false);
|
||||
RzBinFile *bf = rz_bin_open(bin, "bins/elf/stabs/stabs_hello", &opt);
|
||||
mu_assert_notnull(bf, "couldn't open file");
|
||||
|
||||
RzBinStabs *stabs = rz_bin_stabs_parse(bf);
|
||||
mu_assert_notnull(stabs, "stabs should be parsed");
|
||||
|
||||
// record parsing
|
||||
mu_assert_eq(rz_vector_len(&stabs->entries), 24, "stab entry count");
|
||||
|
||||
// the first record is the compilation unit header
|
||||
RzBinStabsEntry *hdr = entry_at(stabs, 0);
|
||||
mu_assert_eq(hdr->type, RZ_BIN_STABS_N_UNDF, "header type");
|
||||
|
||||
// source file
|
||||
RzBinStabsEntry *so = entry_at(stabs, 1);
|
||||
mu_assert_eq(so->type, RZ_BIN_STABS_N_SO, "N_SO type");
|
||||
mu_assert_streq(so->string, "stabs_hello.c", "N_SO string");
|
||||
mu_assert_eq(so->value, 0x401136, "N_SO value");
|
||||
|
||||
// function "add"
|
||||
RzBinStabsEntry *fun_add = entry_at(stabs, 3);
|
||||
mu_assert_eq(fun_add->type, RZ_BIN_STABS_N_FUN, "N_FUN add type");
|
||||
mu_assert_true(rz_str_startswith(fun_add->string, "add:"), "N_FUN add name");
|
||||
mu_assert_eq(fun_add->value, 0x401136, "N_FUN add value");
|
||||
|
||||
// function "main"
|
||||
RzBinStabsEntry *fun_main = entry_at(stabs, 14);
|
||||
mu_assert_eq(fun_main->type, RZ_BIN_STABS_N_FUN, "N_FUN main type");
|
||||
mu_assert_true(rz_str_startswith(fun_main->string, "main:"), "N_FUN main name");
|
||||
mu_assert_eq(fun_main->value, 0x401154, "N_FUN main value");
|
||||
|
||||
// nine source line records overall
|
||||
size_t sline = 0;
|
||||
RzBinStabsEntry *e;
|
||||
rz_vector_foreach (&stabs->entries, e) {
|
||||
if (e->type == RZ_BIN_STABS_N_SLINE) {
|
||||
sline++;
|
||||
}
|
||||
}
|
||||
mu_assert_eq(sline, 9, "N_SLINE count");
|
||||
|
||||
// line info: N_SLINE offsets are relative to the enclosing function, the
|
||||
// builder must resolve them to absolute addresses and keep them sorted
|
||||
RzBinSourceLineInfo *li = rz_bin_stabs_source_line_info(stabs);
|
||||
mu_assert_notnull(li, "line info should be built");
|
||||
mu_assert_eq(li->samples_count, 10, "line samples count");
|
||||
|
||||
const ut64 addrs[] = { 0x401136, 0x401144, 0x40114f, 0x401152, 0x401154,
|
||||
0x401160, 0x401172, 0x40118b, 0x401190, 0x401192 };
|
||||
const ut32 lines[] = { 6, 7, 8, 9, 11, 12, 13, 14, 15, 0 };
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
mu_assert_eq(li->samples[i].address, addrs[i], "sample address");
|
||||
mu_assert_eq(li->samples[i].line, lines[i], "sample line");
|
||||
}
|
||||
// every covered sample points at the source file
|
||||
mu_assert_streq(li->samples[0].file, "stabs_hello.c", "sample file");
|
||||
// the trailing sample closes the range, like DW_LNE_end_sequence
|
||||
mu_assert_true(rz_bin_source_line_sample_is_closing(&li->samples[9]), "closing sample");
|
||||
|
||||
rz_bin_source_line_info_free(li);
|
||||
rz_bin_stabs_free(stabs);
|
||||
rz_io_free(io);
|
||||
rz_bin_free(bin);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_stabs_extraction(void) {
|
||||
RzBin *bin = rz_bin_new();
|
||||
RzIO *io = rz_io_new();
|
||||
rz_io_bind(io, &bin->iob);
|
||||
RzBinOptions opt = { 0 };
|
||||
rz_bin_options_init(&opt, 0, 0, 0, false);
|
||||
RzBinFile *bf = rz_bin_open(bin, "bins/elf/stabs/stabs_syms", &opt);
|
||||
mu_assert_notnull(bf, "couldn't open file");
|
||||
|
||||
RzBinStabs *stabs = rz_bin_stabs_parse(bf);
|
||||
mu_assert_notnull(stabs, "stabs should be parsed");
|
||||
|
||||
RzTypeDB *typedb = rz_type_db_new();
|
||||
mu_assert_notnull(typedb, "typedb should be created");
|
||||
|
||||
RzBinStabsDebugInfo *di = rz_bin_stabs_debug_info(stabs, typedb);
|
||||
mu_assert_notnull(di, "debug info should be extracted");
|
||||
|
||||
// 3 globals/statics + 3 functions + 4 parameters + 4 locals
|
||||
mu_assert_eq(rz_pvector_len(&di->symbols), 14, "recovered symbol count");
|
||||
|
||||
// global and static variables, with their resolved types
|
||||
RzBinStabsSymbol *g_counter = find_sym(di, "g_counter");
|
||||
mu_assert_notnull(g_counter, "g_counter recovered");
|
||||
mu_assert_eq(g_counter->kind, RZ_BIN_STABS_SYMBOL_GLOBAL, "g_counter is a global");
|
||||
mu_assert_eq(g_counter->type->kind, RZ_TYPE_KIND_IDENTIFIER, "g_counter type is an identifier");
|
||||
mu_assert_streq(g_counter->type->identifier.name, "int", "g_counter is an int");
|
||||
|
||||
RzBinStabsSymbol *s_buffer = find_sym(di, "s_buffer");
|
||||
mu_assert_notnull(s_buffer, "s_buffer recovered");
|
||||
mu_assert_eq(s_buffer->kind, RZ_BIN_STABS_SYMBOL_STATIC, "s_buffer is a static");
|
||||
mu_assert_eq(s_buffer->value, 0x404040, "s_buffer has its own address");
|
||||
mu_assert_eq(s_buffer->type->kind, RZ_TYPE_KIND_ARRAY, "s_buffer is an array");
|
||||
mu_assert_eq(s_buffer->type->array.count, 16, "s_buffer holds 16 elements");
|
||||
|
||||
RzBinStabsSymbol *g_origin = find_sym(di, "g_origin");
|
||||
mu_assert_notnull(g_origin, "g_origin recovered");
|
||||
mu_assert_eq(g_origin->kind, RZ_BIN_STABS_SYMBOL_GLOBAL, "g_origin is a global");
|
||||
mu_assert_streq(g_origin->type->identifier.name, "point", "g_origin is a struct point");
|
||||
mu_assert_eq(g_origin->type->identifier.kind, RZ_TYPE_IDENTIFIER_KIND_STRUCT, "g_origin identifier is a struct");
|
||||
|
||||
// functions, with return types and addresses
|
||||
RzBinStabsSymbol *add = find_sym(di, "add");
|
||||
mu_assert_notnull(add, "add recovered");
|
||||
mu_assert_eq(add->kind, RZ_BIN_STABS_SYMBOL_FUNCTION, "add is a function");
|
||||
mu_assert_eq(add->value, 0x401136, "add address");
|
||||
mu_assert_streq(add->type->identifier.name, "int", "add returns an int");
|
||||
|
||||
RzBinStabsSymbol *scale = find_sym(di, "scale");
|
||||
mu_assert_notnull(scale, "scale recovered");
|
||||
mu_assert_eq(scale->value, 0x401154, "scale address");
|
||||
mu_assert_streq(scale->type->identifier.name, "long", "scale returns a long");
|
||||
|
||||
// parameters, linked back to their enclosing function
|
||||
RzBinStabsSymbol *a = find_sym(di, "a");
|
||||
mu_assert_notnull(a, "parameter a recovered");
|
||||
mu_assert_eq(a->kind, RZ_BIN_STABS_SYMBOL_PARAMETER, "a is a parameter");
|
||||
mu_assert_eq(a->function, 0x401136, "a belongs to add");
|
||||
mu_assert_streq(a->type->identifier.name, "int", "a is an int");
|
||||
|
||||
RzBinStabsSymbol *p = find_sym(di, "p");
|
||||
mu_assert_notnull(p, "parameter p recovered");
|
||||
mu_assert_eq(p->kind, RZ_BIN_STABS_SYMBOL_PARAMETER, "p is a parameter");
|
||||
mu_assert_eq(p->function, 0x401154, "p belongs to scale");
|
||||
mu_assert_eq(p->type->kind, RZ_TYPE_KIND_POINTER, "p is a pointer");
|
||||
mu_assert_streq(p->type->pointer.type->identifier.name, "point", "p points to point");
|
||||
|
||||
// local variables, linked back to their enclosing function
|
||||
RzBinStabsSymbol *sum = find_sym(di, "sum");
|
||||
mu_assert_notnull(sum, "local sum recovered");
|
||||
mu_assert_eq(sum->kind, RZ_BIN_STABS_SYMBOL_LOCAL, "sum is a local");
|
||||
mu_assert_eq(sum->function, 0x401136, "sum belongs to add");
|
||||
|
||||
RzBinStabsSymbol *c = find_sym(di, "c");
|
||||
mu_assert_notnull(c, "local c recovered");
|
||||
mu_assert_eq(c->kind, RZ_BIN_STABS_SYMBOL_LOCAL, "c is a local");
|
||||
mu_assert_eq(c->function, 0x40118a, "c belongs to main");
|
||||
mu_assert_streq(c->type->identifier.name, "color", "c is an enum color");
|
||||
|
||||
// types registered into the type database: struct, enum and typedef
|
||||
RzBaseType *point = rz_type_db_get_base_type(typedb, "point");
|
||||
mu_assert_notnull(point, "struct point registered");
|
||||
mu_assert_eq(point->kind, RZ_BASE_TYPE_KIND_STRUCT, "point is a struct");
|
||||
mu_assert_eq(rz_vector_len(&point->struct_data.members), 2, "point has two members");
|
||||
RzTypeStructMember *m0 = rz_vector_index_ptr(&point->struct_data.members, 0);
|
||||
mu_assert_streq(m0->name, "x", "first member is x");
|
||||
mu_assert_eq(m0->offset, 0, "x is at offset 0");
|
||||
RzTypeStructMember *m1 = rz_vector_index_ptr(&point->struct_data.members, 1);
|
||||
mu_assert_streq(m1->name, "y", "second member is y");
|
||||
mu_assert_eq(m1->offset, 4, "y is at offset 4");
|
||||
|
||||
RzBaseType *color = rz_type_db_get_base_type(typedb, "color");
|
||||
mu_assert_notnull(color, "enum color registered");
|
||||
mu_assert_eq(color->kind, RZ_BASE_TYPE_KIND_ENUM, "color is an enum");
|
||||
mu_assert_eq(rz_vector_len(&color->enum_data.cases), 3, "color has three cases");
|
||||
RzTypeEnumCase *case0 = rz_vector_index_ptr(&color->enum_data.cases, 0);
|
||||
mu_assert_streq(case0->name, "RED", "first case is RED");
|
||||
mu_assert_eq(case0->val, 0, "RED is 0");
|
||||
RzTypeEnumCase *case2 = rz_vector_index_ptr(&color->enum_data.cases, 2);
|
||||
mu_assert_streq(case2->name, "BLUE", "third case is BLUE");
|
||||
mu_assert_eq(case2->val, 2, "BLUE is 2");
|
||||
|
||||
RzBaseType *uint_t = rz_type_db_get_base_type(typedb, "uint_t");
|
||||
mu_assert_notnull(uint_t, "typedef uint_t registered");
|
||||
mu_assert_eq(uint_t->kind, RZ_BASE_TYPE_KIND_TYPEDEF, "uint_t is a typedef");
|
||||
|
||||
rz_bin_stabs_debug_info_free(di);
|
||||
rz_type_db_free(typedb);
|
||||
rz_bin_stabs_free(stabs);
|
||||
rz_io_free(io);
|
||||
rz_bin_free(bin);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_stabs_truncated_descriptor(void) {
|
||||
// Regression: a struct member whose type descriptor is empty at the end of
|
||||
// the string used to advance the cursor past the NUL terminator, causing a
|
||||
// heap buffer over-read. The truncated type must instead resolve to void.
|
||||
RzBinStabs stabs = { 0 };
|
||||
rz_vector_init(&stabs.entries, sizeof(RzBinStabsEntry), NULL, NULL);
|
||||
RzBinStabsEntry e = { 0 };
|
||||
e.type = RZ_BIN_STABS_N_LSYM;
|
||||
e.string = "trunc:T(1,2)=s4a:"; // member "a" with its type missing at end-of-string
|
||||
e.strx = 1;
|
||||
rz_vector_push(&stabs.entries, &e);
|
||||
|
||||
RzTypeDB *typedb = rz_type_db_new();
|
||||
RzBinStabsDebugInfo *di = rz_bin_stabs_debug_info(&stabs, typedb);
|
||||
mu_assert_notnull(di, "debug info built for a truncated descriptor");
|
||||
|
||||
RzBaseType *bt = rz_type_db_get_base_type(typedb, "trunc");
|
||||
mu_assert_notnull(bt, "truncated struct still registered");
|
||||
mu_assert_eq(bt->kind, RZ_BASE_TYPE_KIND_STRUCT, "trunc is a struct");
|
||||
mu_assert_eq(rz_vector_len(&bt->struct_data.members), 1, "the single member is parsed");
|
||||
RzTypeStructMember *m = rz_vector_index_ptr(&bt->struct_data.members, 0);
|
||||
mu_assert_streq(m->name, "a", "member name is recovered");
|
||||
|
||||
rz_bin_stabs_debug_info_free(di);
|
||||
rz_type_db_free(typedb);
|
||||
rz_vector_fini(&stabs.entries);
|
||||
|
||||
// A global whose type definition is missing right after '=' must likewise
|
||||
// resolve to void without reading past the end of the string.
|
||||
RzBinStabs s2 = { 0 };
|
||||
rz_vector_init(&s2.entries, sizeof(RzBinStabsEntry), NULL, NULL);
|
||||
RzBinStabsEntry e2 = { 0 };
|
||||
e2.type = RZ_BIN_STABS_N_GSYM;
|
||||
e2.string = "g:G(1,2)="; // nothing follows the '='
|
||||
e2.strx = 1;
|
||||
rz_vector_push(&s2.entries, &e2);
|
||||
|
||||
RzTypeDB *db2 = rz_type_db_new();
|
||||
RzBinStabsDebugInfo *di2 = rz_bin_stabs_debug_info(&s2, db2);
|
||||
mu_assert_notnull(di2, "debug info built for an empty descriptor");
|
||||
RzBinStabsSymbol *g = find_sym(di2, "g");
|
||||
mu_assert_notnull(g, "global with an empty descriptor is recovered");
|
||||
mu_assert_notnull(g->type, "global has a type");
|
||||
mu_assert_eq(g->type->kind, RZ_TYPE_KIND_IDENTIFIER, "empty descriptor resolves to an identifier");
|
||||
|
||||
rz_bin_stabs_debug_info_free(di2);
|
||||
rz_type_db_free(db2);
|
||||
rz_vector_fini(&s2.entries);
|
||||
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool all_tests() {
|
||||
mu_run_test(test_stabs);
|
||||
mu_run_test(test_stabs_extraction);
|
||||
mu_run_test(test_stabs_truncated_descriptor);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
mu_main(all_tests)
|
||||
Loading…
Reference in a new issue