482 lines
15 KiB
C++
482 lines
15 KiB
C++
#include "shim/trace/emitter.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "shim/trace/sha256.h"
|
|
|
|
namespace shim::trace {
|
|
|
|
// ---- modes ----------------------------------------------------------------------------------
|
|
|
|
const char* mode_name(Mode m) {
|
|
switch (m) {
|
|
case Mode::Off: return "off";
|
|
case Mode::Trace: return "trace";
|
|
case Mode::Compare: return "compare";
|
|
case Mode::Replace: return "replace";
|
|
}
|
|
return "off";
|
|
}
|
|
|
|
bool parse_mode(const char* text, Mode& out) {
|
|
if (!text) return false;
|
|
if (std::strcmp(text, "off") == 0) { out = Mode::Off; return true; }
|
|
if (std::strcmp(text, "trace") == 0) { out = Mode::Trace; return true; }
|
|
if (std::strcmp(text, "compare") == 0) { out = Mode::Compare; return true; }
|
|
if (std::strcmp(text, "replace") == 0) { out = Mode::Replace; return true; }
|
|
return false;
|
|
}
|
|
|
|
// ---- typed values -----------------------------------------------------------------------------
|
|
|
|
const char* Tv::type_name() const {
|
|
switch (kind) {
|
|
case Bool: return "bool";
|
|
case Int: return itype;
|
|
case Float: return f32 ? "f32" : "f64";
|
|
case Str: return "str";
|
|
case WStr: return "wstr";
|
|
case Ptr: return "ptr";
|
|
case Enum: return "enum";
|
|
case Null: return "null";
|
|
case Bytes: return "bytes";
|
|
case List: return "list";
|
|
case Set: return "set";
|
|
case Struct: return "struct";
|
|
case Json: return "json";
|
|
}
|
|
return "null";
|
|
}
|
|
|
|
Tv& Tv::add(const char* key, Tv v) {
|
|
keys.emplace_back(key);
|
|
items.push_back(std::move(v));
|
|
return *this;
|
|
}
|
|
|
|
namespace tv {
|
|
|
|
namespace {
|
|
Tv make_int(const char* t, long long v) {
|
|
Tv r;
|
|
r.kind = Tv::Int;
|
|
r.itype = t;
|
|
r.i = v;
|
|
return r;
|
|
}
|
|
Tv make_uint(const char* t, unsigned long long v) {
|
|
Tv r;
|
|
r.kind = Tv::Int;
|
|
r.itype = t;
|
|
r.is_unsigned = true;
|
|
r.u = v;
|
|
return r;
|
|
}
|
|
} // namespace
|
|
|
|
Tv boolean(bool v) { Tv r; r.kind = Tv::Bool; r.b = v; return r; }
|
|
Tv i8(std::int8_t v) { return make_int("i8", v); }
|
|
Tv i16(std::int16_t v) { return make_int("i16", v); }
|
|
Tv i32(std::int32_t v) { return make_int("i32", v); }
|
|
Tv i64(std::int64_t v) { return make_int("i64", v); }
|
|
Tv u8(std::uint8_t v) { return make_uint("u8", v); }
|
|
Tv u16(std::uint16_t v) { return make_uint("u16", v); }
|
|
Tv u32(std::uint32_t v) { return make_uint("u32", v); }
|
|
Tv u64(std::uint64_t v) { return make_uint("u64", v); }
|
|
Tv f32(float v) { Tv r; r.kind = Tv::Float; r.f32 = true; r.f = v; return r; }
|
|
Tv f64(double v) { Tv r; r.kind = Tv::Float; r.f = v; return r; }
|
|
|
|
Tv str(const char* s) {
|
|
if (!s) return ptr(static_cast<std::uintptr_t>(0));
|
|
return str(s, std::strlen(s));
|
|
}
|
|
Tv str(const void* bytes, std::size_t n) {
|
|
Tv r;
|
|
r.kind = Tv::Str;
|
|
r.s.assign(static_cast<const char*>(bytes), n);
|
|
return r;
|
|
}
|
|
Tv wstr(const std::uint16_t* units, std::size_t n) {
|
|
Tv r;
|
|
r.kind = Tv::WStr;
|
|
r.w.assign(units, units + n);
|
|
return r;
|
|
}
|
|
Tv wstr(const wchar_t* s) {
|
|
if (!s) return ptr(static_cast<std::uintptr_t>(0));
|
|
Tv r;
|
|
r.kind = Tv::WStr;
|
|
for (; *s; ++s) r.w.push_back(static_cast<std::uint16_t>(*s));
|
|
return r;
|
|
}
|
|
Tv ptr(const void* p) { return ptr(reinterpret_cast<std::uintptr_t>(p)); }
|
|
Tv ptr(std::uintptr_t p) { Tv r; r.kind = Tv::Ptr; r.p = p; return r; }
|
|
Tv enum_(long long v, const char* symbolic) {
|
|
Tv r;
|
|
r.kind = Tv::Enum;
|
|
r.i = v;
|
|
if (symbolic) r.s = symbolic;
|
|
return r;
|
|
}
|
|
Tv null() { return Tv(); }
|
|
|
|
Tv bytes(const void* data, std::size_t n, std::size_t inline_max) {
|
|
Tv r;
|
|
r.kind = Tv::Bytes;
|
|
r.blen = n;
|
|
Sha256::digest_hex(data, n, r.sha);
|
|
const std::uint8_t* p = static_cast<const std::uint8_t*>(data);
|
|
if (n <= inline_max) {
|
|
r.inline_full = true;
|
|
r.data.assign(p, p + n);
|
|
} else {
|
|
r.data.assign(p, p + 32);
|
|
}
|
|
return r;
|
|
}
|
|
Tv list(std::vector<Tv> items) { Tv r; r.kind = Tv::List; r.items = std::move(items); return r; }
|
|
Tv set(std::vector<Tv> items) { Tv r; r.kind = Tv::Set; r.items = std::move(items); return r; }
|
|
Tv struct_() { Tv r; r.kind = Tv::Struct; return r; }
|
|
Tv json(const char* canonical_text) { Tv r; r.kind = Tv::Json; r.s = canonical_text; return r; }
|
|
|
|
} // namespace tv
|
|
|
|
// ---- output buffer --------------------------------------------------------------------------------
|
|
|
|
Buf::Buf() { grow(4096); }
|
|
Buf::~Buf() { std::free(data_); }
|
|
|
|
bool Buf::grow(std::size_t need) {
|
|
if (need <= cap_) return true;
|
|
std::size_t ncap = cap_ ? cap_ : 4096;
|
|
while (ncap < need) ncap *= 2;
|
|
char* nd = static_cast<char*>(std::realloc(data_, ncap));
|
|
if (!nd) {
|
|
overflow_ = true;
|
|
return false;
|
|
}
|
|
data_ = nd;
|
|
cap_ = ncap;
|
|
return true;
|
|
}
|
|
|
|
void Buf::put(char c) {
|
|
if (len_ + 1 > cap_ && !grow(len_ + 1)) return;
|
|
data_[len_++] = c;
|
|
}
|
|
|
|
void Buf::put(const void* bytes, std::size_t n) {
|
|
if (!n) return;
|
|
if (len_ + n > cap_ && !grow(len_ + n)) return;
|
|
std::memcpy(data_ + len_, bytes, n);
|
|
len_ += n;
|
|
}
|
|
|
|
void Buf::puts(const char* s) { put(s, std::strlen(s)); }
|
|
|
|
void Buf::printf(const char* fmt, ...) {
|
|
char tmp[128];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
const int n = std::vsnprintf(tmp, sizeof tmp, fmt, ap);
|
|
va_end(ap);
|
|
if (n < 0) {
|
|
overflow_ = true;
|
|
return;
|
|
}
|
|
if (static_cast<std::size_t>(n) < sizeof tmp) {
|
|
put(tmp, static_cast<std::size_t>(n));
|
|
return;
|
|
}
|
|
if (!grow(len_ + static_cast<std::size_t>(n) + 1)) return;
|
|
va_start(ap, fmt);
|
|
std::vsnprintf(data_ + len_, static_cast<std::size_t>(n) + 1, fmt, ap);
|
|
va_end(ap);
|
|
len_ += static_cast<std::size_t>(n);
|
|
}
|
|
|
|
void Buf::clear() {
|
|
len_ = 0;
|
|
overflow_ = false;
|
|
}
|
|
|
|
// ---- escaping / numbers -------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
const char* const kHex = "0123456789abcdef";
|
|
|
|
inline void put_u_escape(Buf& out, unsigned unit) {
|
|
char e[6] = {'\\', 'u', kHex[(unit >> 12) & 15], kHex[(unit >> 8) & 15], kHex[(unit >> 4) & 15], kHex[unit & 15]};
|
|
out.put(e, 6);
|
|
}
|
|
|
|
// One unit of either string flavour (bytes for str, UTF-16 units for wstr): mkfixture.esc().
|
|
inline void put_unit(Buf& out, unsigned unit) {
|
|
switch (unit) {
|
|
case '"': out.put("\\\"", 2); return;
|
|
case '\\': out.put("\\\\", 2); return;
|
|
case '\n': out.put("\\n", 2); return;
|
|
case '\r': out.put("\\r", 2); return;
|
|
case '\t': out.put("\\t", 2); return;
|
|
default:
|
|
if (unit < 0x20 || unit >= 0x7f) put_u_escape(out, unit);
|
|
else out.put(static_cast<char>(unit));
|
|
}
|
|
}
|
|
|
|
void put_hex(Buf& out, const std::uint8_t* p, std::size_t n) {
|
|
for (std::size_t i = 0; i < n; ++i) {
|
|
out.put(kHex[p[i] >> 4]);
|
|
out.put(kHex[p[i] & 15]);
|
|
}
|
|
}
|
|
|
|
const long long kTwo53 = 1LL << 53;
|
|
|
|
} // namespace
|
|
|
|
void emit_esc(Buf& out, const char* bytes, std::size_t n) {
|
|
out.put('"');
|
|
for (std::size_t i = 0; i < n; ++i) put_unit(out, static_cast<unsigned char>(bytes[i]));
|
|
out.put('"');
|
|
}
|
|
|
|
void emit_esc(Buf& out, const std::string& s) { emit_esc(out, s.data(), s.size()); }
|
|
|
|
void emit_esc_w(Buf& out, const std::uint16_t* units, std::size_t n) {
|
|
out.put('"');
|
|
for (std::size_t i = 0; i < n; ++i) put_unit(out, units[i]);
|
|
out.put('"');
|
|
}
|
|
|
|
// Integers above 2^53 in magnitude go out as decimal strings (section 3): mkfixture.u64().
|
|
void emit_int(Buf& out, long long v) {
|
|
const bool quote = v >= kTwo53 || v <= -kTwo53;
|
|
if (quote) out.put('"');
|
|
out.printf("%lld", v);
|
|
if (quote) out.put('"');
|
|
}
|
|
|
|
void emit_uint(Buf& out, unsigned long long v) {
|
|
const bool quote = v >= static_cast<unsigned long long>(kTwo53);
|
|
if (quote) out.put('"');
|
|
out.printf("%llu", v);
|
|
if (quote) out.put('"');
|
|
}
|
|
|
|
// mkfixture.fmt_num() for floats.
|
|
void emit_float(Buf& out, double v, bool f32) {
|
|
if (std::isnan(v)) { out.puts("\"nan\""); return; }
|
|
if (std::isinf(v)) { out.puts(v > 0 ? "\"inf\"" : "\"-inf\""); return; }
|
|
out.printf(f32 ? "%.9g" : "%.17g", v);
|
|
}
|
|
|
|
// ---- typed values ----------------------------------------------------------------------------------
|
|
|
|
// mkfixture.emit_tv(): {"t":..., ("n","sha256","hex"|"head") | "v":... [,"name":...]} [,"n":...]
|
|
void emit_tv(Buf& out, const Tv& v) {
|
|
out.puts("{\"t\":");
|
|
emit_esc(out, v.type_name(), std::strlen(v.type_name()));
|
|
if (v.kind == Tv::Bytes) {
|
|
out.printf(",\"n\":%llu,\"sha256\":\"", static_cast<unsigned long long>(v.blen));
|
|
out.puts(v.sha);
|
|
out.puts(v.inline_full ? "\",\"hex\":\"" : "\",\"head\":\"");
|
|
put_hex(out, v.data.data(), v.data.size());
|
|
out.puts("\"}");
|
|
return;
|
|
}
|
|
out.puts(",\"v\":");
|
|
switch (v.kind) {
|
|
case Tv::Bool: out.puts(v.b ? "true" : "false"); break;
|
|
case Tv::Int:
|
|
if (v.is_unsigned) emit_uint(out, v.u);
|
|
else emit_int(out, v.i);
|
|
break;
|
|
case Tv::Float: emit_float(out, v.f, v.f32); break;
|
|
case Tv::Str: emit_esc(out, v.s); break;
|
|
case Tv::WStr: emit_esc_w(out, v.w.data(), v.w.size()); break;
|
|
case Tv::Ptr: out.printf("\"0x%08llx\"", static_cast<unsigned long long>(v.p)); break;
|
|
case Tv::Enum: out.printf("%lld", v.i); break;
|
|
case Tv::Null: out.puts("null"); break;
|
|
case Tv::List:
|
|
case Tv::Set:
|
|
out.put('[');
|
|
for (std::size_t i = 0; i < v.items.size(); ++i) {
|
|
if (i) out.put(',');
|
|
emit_tv(out, v.items[i]);
|
|
}
|
|
out.put(']');
|
|
break;
|
|
case Tv::Struct:
|
|
out.put('{');
|
|
for (std::size_t i = 0; i < v.items.size(); ++i) {
|
|
if (i) out.put(',');
|
|
emit_esc(out, v.keys[i]);
|
|
out.put(':');
|
|
emit_tv(out, v.items[i]);
|
|
}
|
|
out.put('}');
|
|
break;
|
|
case Tv::Json: out.puts(v.s.c_str()); break;
|
|
case Tv::Bytes: break; // handled above
|
|
}
|
|
if (v.kind == Tv::Enum && !v.s.empty()) {
|
|
out.puts(",\"name\":");
|
|
emit_esc(out, v.s);
|
|
}
|
|
if (!v.name.empty()) {
|
|
out.puts(",\"n\":");
|
|
emit_esc(out, v.name);
|
|
}
|
|
out.put('}');
|
|
}
|
|
|
|
std::string canonical(const Tv& v) {
|
|
Buf b;
|
|
emit_tv(b, v);
|
|
return b.str();
|
|
}
|
|
|
|
// ---- records ----------------------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
|
|
void emit_side(Buf& out, const std::vector<SideEntry>& side) {
|
|
out.put('{');
|
|
for (std::size_t i = 0; i < side.size(); ++i) {
|
|
const SideEntry& e = side[i];
|
|
if (i) out.put(',');
|
|
emit_esc(out, e.name);
|
|
out.puts(":{");
|
|
if (e.before_kind == SideEntry::IsNull) {
|
|
out.puts("\"before\":null,");
|
|
} else if (e.before_kind == SideEntry::Value) {
|
|
out.puts("\"before\":");
|
|
emit_tv(out, e.before);
|
|
out.put(',');
|
|
}
|
|
out.puts("\"after\":");
|
|
emit_tv(out, e.after);
|
|
out.put('}');
|
|
}
|
|
out.put('}');
|
|
}
|
|
|
|
void emit_ours_side(Buf& out, const std::vector<OursSide>& side) {
|
|
out.put('{');
|
|
for (std::size_t i = 0; i < side.size(); ++i) {
|
|
if (i) out.put(',');
|
|
emit_esc(out, side[i].name);
|
|
out.puts(":{\"after\":");
|
|
emit_tv(out, side[i].after);
|
|
out.put('}');
|
|
}
|
|
out.put('}');
|
|
}
|
|
|
|
void emit_opt_tv(Buf& out, const std::optional<Tv>& v) {
|
|
if (v) emit_tv(out, *v);
|
|
else out.puts("null");
|
|
}
|
|
|
|
void emit_diff(Buf& out, const std::vector<DiffEntry>& diff) {
|
|
out.put('[');
|
|
for (std::size_t i = 0; i < diff.size(); ++i) {
|
|
const DiffEntry& d = diff[i];
|
|
if (i) out.put(',');
|
|
out.puts("{\"path\":");
|
|
emit_esc(out, d.path);
|
|
out.puts(",\"why\":");
|
|
emit_esc(out, d.why, std::strlen(d.why));
|
|
out.puts(",\"orig\":");
|
|
if (!d.orig_raw.empty()) out.puts(d.orig_raw.c_str());
|
|
else emit_opt_tv(out, d.orig);
|
|
out.puts(",\"ours\":");
|
|
if (!d.ours_raw.empty()) out.puts(d.ours_raw.c_str());
|
|
else emit_opt_tv(out, d.ours);
|
|
if (d.first_diff_offset >= 0) out.printf(",\"first_diff_offset\":%lld", d.first_diff_offset);
|
|
if (!d.note.empty()) {
|
|
out.puts(",\"note\":");
|
|
emit_esc(out, d.note);
|
|
}
|
|
out.put('}');
|
|
}
|
|
out.put(']');
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// mkfixture.emit_record(): fixed KEY_ORDER, optional keys omitted.
|
|
void emit_record(Buf& out, const Record& r) {
|
|
out.printf("{\"ts\":%lld,\"hook\":", static_cast<long long>(r.ts));
|
|
emit_esc(out, r.hook);
|
|
out.printf(",\"mode\":\"%s\",\"call_id\":%llu,\"thread\":%llu", mode_name(r.mode),
|
|
static_cast<unsigned long long>(r.call_id), static_cast<unsigned long long>(r.thread));
|
|
if (r.depth >= 0) out.printf(",\"depth\":%d", r.depth);
|
|
out.puts(",\"args\":[");
|
|
for (std::size_t i = 0; i < r.args.size(); ++i) {
|
|
if (i) out.put(',');
|
|
emit_tv(out, r.args[i]);
|
|
}
|
|
out.puts("],\"ret\":");
|
|
emit_opt_tv(out, r.ret);
|
|
out.puts(",\"side\":");
|
|
emit_side(out, r.side);
|
|
if (r.has_ours) {
|
|
out.puts(",\"ours\":{\"ret\":");
|
|
emit_opt_tv(out, r.ours_ret);
|
|
out.puts(",\"side\":");
|
|
emit_ours_side(out, r.ours_side);
|
|
out.put('}');
|
|
}
|
|
if (r.diverged >= 0) out.puts(r.diverged ? ",\"diverged\":true" : ",\"diverged\":false");
|
|
if (r.has_diff) {
|
|
out.puts(",\"diff\":");
|
|
emit_diff(out, r.diff);
|
|
}
|
|
if (r.err) {
|
|
out.puts(",\"err\":");
|
|
emit_esc(out, *r.err);
|
|
}
|
|
if (r.note) {
|
|
out.puts(",\"note\":");
|
|
emit_esc(out, *r.note);
|
|
}
|
|
out.puts("}\n");
|
|
}
|
|
|
|
// mkfixture.emit_meta() over the dict shape mkfixture.meta() builds.
|
|
void emit_meta(Buf& out, const Meta& m) {
|
|
out.puts("{\"meta\":{\"format\":1,\"build\":");
|
|
emit_esc(out, m.build);
|
|
out.puts(",\"exe_sha256\":");
|
|
emit_esc(out, m.exe_sha256);
|
|
out.puts(",\"started\":");
|
|
emit_esc(out, m.started);
|
|
out.printf(",\"inline_max\":%u,\"hooks\":{", m.inline_max);
|
|
for (std::size_t i = 0; i < m.hooks.size(); ++i) {
|
|
const HookPolicy& p = m.hooks[i].second;
|
|
if (i) out.put(',');
|
|
emit_esc(out, m.hooks[i].first);
|
|
out.puts(":{\"ftol\":");
|
|
emit_float(out, p.ftol, false);
|
|
out.printf(",\"ftol_kind\":\"%s\",\"ptr\":\"%s\"", p.ftol_kind, p.ptr_exact ? "exact" : "ignore");
|
|
if (!p.unordered.empty()) {
|
|
out.puts(",\"unordered\":[");
|
|
for (std::size_t j = 0; j < p.unordered.size(); ++j) {
|
|
if (j) out.put(',');
|
|
emit_esc(out, p.unordered[j]);
|
|
}
|
|
out.put(']');
|
|
}
|
|
out.put('}');
|
|
}
|
|
out.puts("}}}\n");
|
|
}
|
|
|
|
} // namespace shim::trace
|