mirror of
https://github.com/radareorg/r2xsql.git
synced 2026-11-07 22:42:50 -10:00
Compare commits
1
Commits
more-fixes
...
demangle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f748970e45 |
No files matched your search
+5
-10
@@ -1245,7 +1245,7 @@ its own, the identical trade-off `rop_gadgets` already made for its own
|
||||
libr).
|
||||
|
||||
#### `demangle` (read-only)
|
||||
One row per query: demangles an ARBITRARY mangled name via `iD`/`iDj`.
|
||||
One row per query: demangles an ARBITRARY mangled name via `r_bin_demangle`.
|
||||
Distinct from `funcs.name`/`imports.name`, which already demangle
|
||||
automatically (gated on `bin.demangle`) for symbols radare2 already knows
|
||||
from the loaded binary — `demangle` answers the same question for a string
|
||||
@@ -1269,15 +1269,10 @@ what `lang` says — verified live that an unrecognized `lang` string does not
|
||||
abort demangling, it just falls through to auto-detection. `mangled` is NOT
|
||||
charset-restricted the way `assemble`/`search_asm`'s `asm` is: MSVC manglings
|
||||
use `?`/`@`/`$` throughout, which a disassembly-text allowlist would reject.
|
||||
Instead this table dispatches through `Backend::cmd_noeval`, seeked to a
|
||||
fixed anchor (0, unobserved by `iD`'s handler either way — same shape as
|
||||
`types`' addr-less `td`/`t-` dispatch), falling back to the
|
||||
`json_utils::r2_quote`-wrapped plain command on every backend that reports
|
||||
cmd_noeval unsupported. Verified live that the injection risk is real: `iDj
|
||||
cxx foo; ?e INJECTED` spliced through a plain `Backend::cmd()` call DOES
|
||||
execute the injected `?e` as a second command, while `cmd_noeval` reaches
|
||||
`iD`'s argument parser with the whole string, `;` included, treated as
|
||||
opaque data.
|
||||
The in-process backend passes the mangled name directly to `r_bin_demangle`,
|
||||
with the current `RBinFile` and `bin.lang` default. This preserves arbitrary
|
||||
input without command parsing. A backend without an in-process RBin API
|
||||
reports an error for this table.
|
||||
|
||||
#### Point-lookup recipes: `name_at` / `section_at` / `string_at`
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
// That TU boundary is what forces the row struct out of its table's anonymous
|
||||
// namespace and into this header: a row struct lives beside its table until the
|
||||
// table is ported, and moves here when a second TU has to build it.
|
||||
// The demangle method also uses this boundary so arbitrary symbol text reaches
|
||||
// RBin directly, without passing through the r2 command parser.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -508,6 +510,11 @@ class Api {
|
||||
virtual void binary_info(std::vector<BinaryRow>& out) = 0;
|
||||
virtual std::string file_path() = 0;
|
||||
virtual std::string engine_version() = 0;
|
||||
// Demangle an arbitrary symbol using the current RBinFile and bin.lang
|
||||
// setting. Empty result means radare2 could not demangle the name; error
|
||||
// receives the same diagnostic as iDj in that case.
|
||||
virtual std::string demangle(const std::string& mangled,
|
||||
const std::string& lang, std::string& error) = 0;
|
||||
virtual bool entry_address(int64_t& out) = 0;
|
||||
virtual void relocs_scan(std::vector<RelocRow>& out) = 0;
|
||||
virtual void type_members_scan(std::vector<MemberRow>& out) = 0;
|
||||
|
||||
@@ -121,6 +121,29 @@ class LibrApi : public Api {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string demangle(const std::string& mangled,
|
||||
const std::string& lang, std::string& error) override {
|
||||
std::lock_guard lock(mu_);
|
||||
error = "Cannot demangle string";
|
||||
if (!core_ || !core_->bin) return {};
|
||||
const char* selected_lang = lang.empty()
|
||||
? r_config_get(core_->config, "bin.lang") : lang.c_str();
|
||||
if ((!selected_lang || !*selected_lang) && lang.empty()) {
|
||||
error = "Set the language in `e bin.lang` or pass a second argument";
|
||||
}
|
||||
RBinFile temporary_file = {};
|
||||
RBinFile* file = core_->bin->cur;
|
||||
if (!file) {
|
||||
temporary_file.rbin = core_->bin;
|
||||
file = &temporary_file;
|
||||
}
|
||||
char* result = r_bin_demangle(file, selected_lang, mangled.c_str(), 0, false);
|
||||
if (!result) return {};
|
||||
std::string demangled(result);
|
||||
free(result);
|
||||
return demangled;
|
||||
}
|
||||
|
||||
bool function_is_start(int64_t addr) override {
|
||||
std::lock_guard lock(mu_);
|
||||
return core_ && core_->anal &&
|
||||
|
||||
@@ -56,22 +56,7 @@ bool boolean(const nlohmann::json& obj, std::string_view key, bool dflt) {
|
||||
return dflt;
|
||||
}
|
||||
|
||||
std::string r2_quote(std::string_view s) {
|
||||
std::string out;
|
||||
out.reserve(s.size() + 2);
|
||||
out.push_back('"');
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '\\': out += "\\\\"; break;
|
||||
case '"': out += "\\\""; break;
|
||||
case '\n': out += "\\n"; break;
|
||||
case '\r': out += "\\r"; break;
|
||||
default: out.push_back(c);
|
||||
}
|
||||
}
|
||||
out.push_back('"');
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
bool valid_flag_name(std::string_view name) {
|
||||
if (name.empty()) return false;
|
||||
|
||||
@@ -31,7 +31,7 @@ int64_t i64_addr(const nlohmann::json& obj, int64_t dflt = 0);
|
||||
|
||||
// Quote a string for use as a shell-ish r2 command argument. r2 itself is
|
||||
// not a shell, but spaces, quotes and backslashes need escaping in CC args.
|
||||
std::string r2_quote(std::string_view s);
|
||||
|
||||
|
||||
// True iff `name` is safe to splice into an r2 flag command unquoted: a
|
||||
// non-empty string of [A-Za-z0-9._$] only. This is roughly r2's own flag-name
|
||||
|
||||
@@ -59,7 +59,7 @@ void register_class_methods_table(Session& s);
|
||||
void register_registers_table(Session& s); // static per-arch register profile (arpj)
|
||||
void register_relocs_table(Session& s); // relocation table (irj)
|
||||
void register_libs_table(Session& s); // linked library list (ilj)
|
||||
void register_demangle_table(Session& s); // one-row demangler (iD/iDj)
|
||||
void register_demangle_table(Session& s); // one-row RBin demangler
|
||||
void register_assemble_table(Session& s); // one-row assembler (pa)
|
||||
void register_search_asm_table(Session& s); // assemble-then-search (/a)
|
||||
void register_read_cstr_table(Session& s); // arbitrary-address cstring read (pszj)
|
||||
|
||||
@@ -4,93 +4,25 @@
|
||||
// This file is licensed under the Human-Origin Source License v1.0.
|
||||
// See LICENSE.
|
||||
//
|
||||
// demangle -- one-row-per-query demangler over radare2's `iD`/`iDj` (verified
|
||||
// against the CURRENT radare2 source, `libr/core/cmd_info.inc.c`'s
|
||||
// `cmd_info_demangle` + `libr/bin/demangle.c`'s `r_bin_demangle`/
|
||||
// `r_bin_demangle_type`, not assumed from a citation). This closes a REAL
|
||||
// gap: `funcs.name` and `imports.name` already demangle automatically (via
|
||||
// `r_core_anal_fcn_name`/`r_bin_demangle` respectively, gated on the
|
||||
// `bin.demangle` config) for symbols radare2 already knows about from the
|
||||
// loaded binary, but there was no SQL surface to demangle an ARBITRARY
|
||||
// string that isn't necessarily any table's row.
|
||||
//
|
||||
// The `mangled` hidden input is NOT charset-restricted the way
|
||||
// `assemble`/`search_asm`'s `asm` input is (see tables_assemble.cpp):
|
||||
// real-world manglings routinely use characters a disassembly-text
|
||||
// allowlist would reject -- MSVC uses `?`/`@`/`$` throughout
|
||||
// (`?foo@@YAHXZ`), Rust and Itanium C++ use `$`-escaped substitutions
|
||||
// heavily. So this table uses the SAME structural no-eval dispatch
|
||||
// (Backend::cmd_noeval) `types`' `td` already pilots
|
||||
// for an addr-less command with a genuinely unrestrictable free-text
|
||||
// argument -- verified live (not assumed) that the injection risk is real:
|
||||
// `iDj cxx foo; ?e INJECTED` through a plain `Backend::cmd()` splice DOES
|
||||
// execute the injected `?e` as a second command (radare2's own `;`
|
||||
// chaining), while `cmd_noeval` reaches `iD`'s single argument parser with
|
||||
// the whole string, `;` included, treated as opaque data.
|
||||
//
|
||||
// `iD`/`iDj` carries no address in its own grammar at all (same shape as
|
||||
// `td`/`t-`), so this dispatches seeked to a fixed anchor (0, same constant
|
||||
// choice as `table_utils::kTypeSystemAnchorAddr` -- kept as a small local
|
||||
// copy rather than reusing that name directly, since this table has nothing
|
||||
// to do with the type system; the anchor value is arbitrary and unobserved
|
||||
// by `iD`'s handler either way, verified against `cmd_info_demangle`, which
|
||||
// reads only its own `lang`/`text` arguments, never `core->addr`).
|
||||
//
|
||||
// `lang` is passed through UNVALIDATED against radare2's own known-language
|
||||
// list: verified live that `r_bin_demangle_type()` returning
|
||||
// `R_BIN_LANG_NONE` for an unrecognized string does NOT abort the call --
|
||||
// `cmd_info_demangle` still invokes `r_bin_demangle(bf, lang, text, ...)`,
|
||||
// which runs its OWN auto-detection from the mangled text's prefix
|
||||
// (`_Z`/`__Z`/`?`/...) regardless of what `lang` said. So a caller-supplied
|
||||
// `lang` is always advisory, never validated client-side to match; this
|
||||
// table simply forwards it and lets radare2 do what it already does.
|
||||
//
|
||||
// `iDj`'s own JSON always echoes `lang` back VERBATIM (the input string,
|
||||
// not radare2's actually-auto-detected type) -- confirmed live with
|
||||
// `iDj bogus _ZN3Foo3barEv`, which still demangled successfully via
|
||||
// auto-detection while echoing `"lang":"bogus"`. This table does not
|
||||
// surface that echo at all (mangled/lang are pure hidden inputs, never
|
||||
// duplicated as output columns, matching byte_search's own
|
||||
// pattern-is-hidden-only convention) precisely to avoid presenting that
|
||||
// potentially-misleading echo as if it were the resolved demangler.
|
||||
//
|
||||
// No JSON mode exists for the empty-`text` or unparseable-argument cases
|
||||
// either -- `iDj` still emits a well-formed `{"lang":...,"mangled":...,
|
||||
// "error":"..."}` object in every failure mode exercised (empty text,
|
||||
// non-mangled text, unrecognized lang), so `error` is always populated
|
||||
// exactly when `demangled` is not, with no separate "malformed command"
|
||||
// case to handle.
|
||||
//
|
||||
// Command-path-only, no C-API producer: a single demangle call has no N+1
|
||||
// or full-scan cost shape to collapse (same "one full-scan-equivalent
|
||||
// command, no per-row cost" reasoning `bininfo`/`rop_gadgets` already
|
||||
// documented for themselves), so both backends share the identical
|
||||
// command-based implementation and cannot disagree.
|
||||
// demangle -- one row for an arbitrary mangled name. Demangling always uses
|
||||
// r_bin_demangle through the in-process direct::Api.
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <r2xsql/session.hpp>
|
||||
#include <r2xsql/backend.hpp>
|
||||
#include <xsql/vtable.hpp>
|
||||
|
||||
#include "tables.hpp"
|
||||
#include "json_utils.hpp"
|
||||
#include "table_utils.hpp"
|
||||
#include "direct.hpp"
|
||||
|
||||
namespace r2xsql {
|
||||
|
||||
namespace {
|
||||
|
||||
// Arbitrary, unobserved by `iD`'s handler (see file header) -- kept as a
|
||||
// small local constant rather than reusing table_utils::kTypeSystemAnchorAddr
|
||||
// so this table reads as self-contained, not borrowed from the type system.
|
||||
constexpr int64_t kDemangleAnchorAddr = 0;
|
||||
|
||||
struct DemangleRow {
|
||||
std::string demangled; // empty iff demangling failed (never a legitimate
|
||||
// empty symbol name)
|
||||
@@ -99,18 +31,6 @@ struct DemangleRow {
|
||||
|
||||
enum Col { kDemangled = 0, kError = 1, kMangled = 2, kLang = 3 };
|
||||
|
||||
// Seeked-to-a-fixed-anchor no-eval dispatch for `iDj`, mirroring
|
||||
// table_utils::dispatch_type_write's shape exactly (try cmd_noeval first,
|
||||
// fall back to the r2_quote-wrapped plain command on every backend that
|
||||
// reports it unsupported) -- a local copy, not a shared call, since this
|
||||
// table's anchor and command shape are its own (see file header).
|
||||
std::string dispatch_demangle(Backend* be, const std::string& noeval_command,
|
||||
const std::string& fallback_command) {
|
||||
std::string out;
|
||||
if (be->cmd_noeval(kDemangleAnchorAddr, noeval_command, &out)) return out;
|
||||
return be->cmd(fallback_command);
|
||||
}
|
||||
|
||||
DemangleRow run_demangle(Backend* be, const std::string& mangled,
|
||||
const std::string& lang) {
|
||||
DemangleRow row;
|
||||
@@ -118,31 +38,13 @@ DemangleRow run_demangle(Backend* be, const std::string& mangled,
|
||||
row.error = "demangle: mangled must not be empty";
|
||||
return row;
|
||||
}
|
||||
// Two-arg form when lang is given ("iDj <lang> <mangled>"), single-arg
|
||||
// form otherwise ("iDj <mangled>", radare2 defaults lang from its own
|
||||
// bin.lang config) -- matches iD's own grammar (see file header); this
|
||||
// table never synthesizes a placeholder lang token to keep the shapes
|
||||
// separate, since args="" lang would misparse a mangled name containing
|
||||
// its own leading space.
|
||||
const std::string plain =
|
||||
lang.empty() ? ("iDj " + mangled) : ("iDj " + lang + " " + mangled);
|
||||
const std::string out = dispatch_demangle(be, plain, json_utils::r2_quote(plain));
|
||||
|
||||
nlohmann::json j = nlohmann::json::parse(out, nullptr, false);
|
||||
if (j.is_discarded() || !j.is_object()) {
|
||||
row.error = "demangle: radare2 returned no result (iDj produced no "
|
||||
"parseable output)";
|
||||
auto* api = be->direct();
|
||||
if (!api) {
|
||||
row.error = "demangle requires the in-process libr backend";
|
||||
return row;
|
||||
}
|
||||
if (j.contains("error") && j["error"].is_string()) {
|
||||
row.error = j["error"].get<std::string>();
|
||||
return row;
|
||||
}
|
||||
row.demangled = json_utils::str(j, "demangled");
|
||||
if (row.demangled.empty()) {
|
||||
row.error = "demangle: radare2 produced neither a demangled name nor an "
|
||||
"error (unexpected iDj shape)";
|
||||
}
|
||||
row.demangled = api->demangle(mangled, lang, row.error);
|
||||
if (!row.demangled.empty()) row.error.clear();
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user