Insert labels right at immediates

This commit is contained in:
York Jasper Niebuhr 2026-04-03 14:32:45 +02:00
parent 9555c48024
commit ce7e75ecc9
16 changed files with 335 additions and 98 deletions

View File

@ -50,9 +50,9 @@ target <name> <local uid> <size> <field count>
f <offset> <size> <alignment> <flags> f <offset> <size> <alignment> <flags>
f <offset> <size> <alignment> <flags> f <offset> <size> <alignment> <flags>
... ...
ipin <label> <target uid> <field offset> ipin <label> <target uid> <field offset> <immediate size>
ipin <label> <target uid> <field offset> ipin <label> <target uid> <field offset> <immediate size>
ipin <label> <target uid> <field offset> ipin <label> <target uid> <field offset> <immediate size>
... ...
dpin <local/global> <symbol> <offset> <level> <target uid> dpin <local/global> <symbol> <offset> <level> <target uid>
dpin <local/global> <symbol> <offset> <level> <target uid> dpin <local/global> <symbol> <offset> <level> <target uid>
@ -157,7 +157,8 @@ static bool accumulate_file(const fs::path& path) {
continue; continue;
} else if (type == "ipin") { } else if (type == "ipin") {
IPIN ipin; IPIN ipin;
if (!(iss >> ipin.symbol) || !(iss >> ipin.local_target) || !(iss >> ipin.field_offset)) { if (!(iss >> ipin.symbol) || !(iss >> ipin.local_target) ||
!(iss >> ipin.field_offset) || !(iss >> ipin.imm_size)) {
std::cerr << "Failed to parse ipin declaration!" << std::endl; std::cerr << "Failed to parse ipin declaration!" << std::endl;
return false; return false;
} }

View File

@ -10,13 +10,12 @@
struct IPIN { struct IPIN {
struct HIT { struct HIT {
uint64_t vaddr; uint64_t vaddr;
uint32_t imm_offset;
uint32_t imm_size;
}; };
std::string symbol; std::string symbol;
std::size_t local_target; std::size_t local_target;
std::size_t field_offset; std::size_t field_offset;
std::size_t imm_size;
std::optional<HIT> hit; std::optional<HIT> hit;
}; };

View File

@ -22,7 +22,6 @@ Notes:
using namespace LIEF::ELF; using namespace LIEF::ELF;
static bool disassemble_ipin(const Section* text, IPIN::HIT& pin);
static bool assemble_patcher_program(uint64_t vaddr_pivot, std::vector<uint8_t>& program); static bool assemble_patcher_program(uint64_t vaddr_pivot, std::vector<uint8_t>& program);
int main(int argc, char** argv) { int main(int argc, char** argv) {
@ -104,24 +103,13 @@ int main(int argc, char** argv) {
return 1; return 1;
} }
// For each ipin, disassemble instruction and find immediate offset // Make sure all ipins were located
const Section* text = bin->get_section(".text");
if (!text) {
std::cerr << "Unable to locate .text section for ipin disassembly!" << std::endl;
return 1;
}
for (auto& [cu_uid, cu] : units) { for (auto& [cu_uid, cu] : units) {
for (auto& [_, ipin] : cu.ipins) { for (auto& [_, ipin] : cu.ipins) {
if (!ipin.hit.has_value()) { if (!ipin.hit.has_value()) {
std::cerr << "Encountered ipin without vaddr!" << std::endl; std::cerr << "Encountered ipin without vaddr!" << std::endl;
return 1; return 1;
} }
if (!disassemble_ipin(text, ipin.hit.value())) {
std::cerr << "Failed to disassemble ipin!" << std::endl;
return 1;
}
} }
} }
@ -259,10 +247,12 @@ bool assemble_patcher_program(uint64_t vaddr_pivot, std::vector<uint8_t>& progra
// Dump ipins with addresses relative to vaddr_pivot // Dump ipins with addresses relative to vaddr_pivot
auto append_ipin = [&](const CU& cu, const IPIN& ipin) -> bool { auto append_ipin = [&](const CU& cu, const IPIN& ipin) -> bool {
inst.opcode = SPSLR_IPATCH; inst.opcode = SPSLR_IPATCH;
inst.op0.ipatch_ptr = ipin.hit->vaddr + ipin.hit->imm_offset - vaddr_pivot; inst.op0.ipatch_ptr = ipin.hit->vaddr - vaddr_pivot;
inst.op1.ipatch_size = ipin.hit->imm_size; inst.op1.ipatch_size = ipin.imm_size;
inst.op2.ipatch_target = cu.local_targets.at(ipin.local_target); inst.op2.ipatch_target = cu.local_targets.at(ipin.local_target);
std::cout << "IPIN found at 0x" << std::hex << ipin.hit->vaddr << std::dec << std::endl;
if (!targets.at(inst.op2.ipatch_target).fields.contains(ipin.field_offset)) if (!targets.at(inst.op2.ipatch_target).fields.contains(ipin.field_offset))
return false; return false;
@ -319,31 +309,3 @@ bool assemble_patcher_program(uint64_t vaddr_pivot, std::vector<uint8_t>& progra
return true; return true;
} }
bool disassemble_ipin(const Section* text, IPIN::HIT& pin) {
if (!text)
return false;
uint64_t text_begin = text->virtual_address();
uint64_t text_size = text->size();
auto text_data = text->content();
uint64_t pin_addr = pin.vaddr;
if (pin_addr < text_begin || pin_addr >= text_begin + text_size)
return false;
uint64_t pin_offset = pin_addr - text_begin;
// 32 bit mov of immediate to 64 bit register: 0x48 0xc7 [8 bit reg] [32 bit immediate]
if (text_data[pin_offset] != 0x48 || text_data[pin_offset + 1] != 0xc7) {
std::cerr << "Ipin uses not yet handled instruction!" << std::endl;
return false;
}
pin.imm_offset = 3;
pin.imm_size = 4;
return true;
}

View File

@ -99,8 +99,6 @@ bool associate_symbols() {
ipin.hit.emplace(); ipin.hit.emplace();
ipin.hit->vaddr = lsyms.symbols.at(ipin_sym); ipin.hit->vaddr = lsyms.symbols.at(ipin_sym);
ipin.hit->imm_offset = 0;
ipin.hit->imm_size = 0;
} }
for (auto& [dpin_sym, dpin] : cu.dpins) { for (auto& [dpin_sym, dpin] : cu.dpins) {

View File

@ -10,4 +10,5 @@ target_include_directories(spslr_pinpoint PRIVATE ${GCC_PLUGIN_PATH}/include ${C
add_subdirectory(stage0) add_subdirectory(stage0)
add_subdirectory(stage1) add_subdirectory(stage1)
add_subdirectory(stage2)
add_subdirectory(final) add_subdirectory(final)

View File

@ -1,7 +1,7 @@
#include <iostream> #include <iostream>
#include <final.h> #include <final.h>
#include <stage0.h> #include <stage0.h>
#include <stage1.h> #include <stage2.h>
#include <string> #include <string>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@ -153,9 +153,9 @@ void on_finish_unit(void* plugin_data, void* user_data) {
// Dump all instruction pins // Dump all instruction pins
for (const auto& [uid, ipin] : S1InstructionPin::all()) { for (const auto& [uid, ipin] : s2_pins()) {
// ipin <label> <target uid> <field offset> // ipin <symbol> <target uid> <field offset> <immediate size>
out << "ipin " << SPSLR_PINPOINT_STAGE1_PIN << uid << " " out << "ipin " << ipin.symbol << " "
<< ipin.target << " " << ipin.offset << std::endl; << ipin.target << " " << ipin.offset << " " << ipin.imm_size << std::endl;
} }
} }

View File

@ -5,6 +5,7 @@
#include <stage0.h> #include <stage0.h>
#include <stage1.h> #include <stage1.h>
#include <stage2.h>
#include <final.h> #include <final.h>
int plugin_is_GPL_compatible; int plugin_is_GPL_compatible;
@ -47,7 +48,7 @@ int plugin_init(struct plugin_name_args* plugin_info, struct plugin_gcc_version*
separate_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER; separate_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER;
register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &separate_offset_pass_info); register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &separate_offset_pass_info);
// Stage 1 -> stage 0 separators are replaced with inline assembly // Stage 1 -> stage 0 separators are replaced with inline assembly markers
struct register_pass_info asm_offset_pass_info; struct register_pass_info asm_offset_pass_info;
asm_offset_pass_info.pass = new asm_offset_pass(nullptr); asm_offset_pass_info.pass = new asm_offset_pass(nullptr);
@ -56,6 +57,15 @@ int plugin_init(struct plugin_name_args* plugin_info, struct plugin_gcc_version*
asm_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER; asm_offset_pass_info.pos_op = PASS_POS_INSERT_AFTER;
register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &asm_offset_pass_info); register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &asm_offset_pass_info);
// Stage 2 -> stage 1 markers are replaced with assembled offset-load instructions with labeled immediates
struct register_pass_info rtl_pin_lower_pass_info;
rtl_pin_lower_pass_info.pass = new rtl_pin_lower_pass(nullptr);
rtl_pin_lower_pass_info.ref_pass_instance_number = 1;
rtl_pin_lower_pass_info.reference_pass_name = "final";
rtl_pin_lower_pass_info.pos_op = PASS_POS_INSERT_BEFORE;
register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, nullptr, &rtl_pin_lower_pass_info);
// Final stage -> dump accumulated information // Final stage -> dump accumulated information
register_callback(plugin_info->base_name, PLUGIN_FINISH_UNIT, on_finish_unit, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH_UNIT, on_finish_unit, NULL);

View File

@ -2,6 +2,6 @@
#define SPSLR_ATTRIBUTE "spslr" #define SPSLR_ATTRIBUTE "spslr"
#define SPSLR_PINPOINT_STAGE0_SEPARATOR "__spslr_offsetof" #define SPSLR_PINPOINT_STAGE0_SEPARATOR "__spslr_offsetof"
#define SPSLR_PINPOINT_STAGE1_PIN "__spslr_ipin_" /* suffixed with "<uid>" */
#define SPSLR_PINPOINT_CU_UID_LABEL "__spslr_cu_" /* suffixed with "<uid>" */ #define SPSLR_PINPOINT_CU_UID_LABEL "__spslr_cu_" /* suffixed with "<uid>" */
#define SPSLR_PINFILE_EXTENSION ".spslr" #define SPSLR_PINFILE_EXTENSION ".spslr"
#define SPSLR_PINPOINT_STAGE2_PIN "__spslr_ipin_" /* suffixed with "<uid>" */

View File

@ -0,0 +1,17 @@
#include <safe-gcc-plugin.h>
#ifndef SAFEGCC_RTL_H
#define SAFEGCC_RTL_H
#include <rtl.h>
#include <rtl-iter.h>
#include <memmodel.h>
#include <emit-rtl.h>
#include <function.h>
#include <expr.h>
#include <hard-reg-set.h>
#undef toupper
#undef tolower
#endif

View File

@ -1,8 +1,9 @@
#include <stage0.h> #include <stage0.h>
#include <stage1.h> #include <stage1.h>
#include <stage2.h>
void on_start_unit(void* plugin_data, void* user_data) { void on_start_unit(void* plugin_data, void* user_data) {
TargetType::reset(); TargetType::reset();
DataPin::reset(); DataPin::reset();
S1InstructionPin::reset(); s2_pins_reset();
} }

View File

@ -4,18 +4,9 @@
#include <pinpoint_error.h> #include <pinpoint_error.h>
#include <pinpoint_config.h> #include <pinpoint_config.h>
static UID next_pin_uid = 0; const char* s1_ipin_marker = "/*spslr_s1_ipin_marker*/";
static std::unordered_map<UID, S1InstructionPin> pins;
const std::unordered_map<UID, S1InstructionPin>& S1InstructionPin::all() {
return pins;
}
void S1InstructionPin::reset() {
pins.clear();
next_pin_uid = 0;
}
/*
static char asm_str_buf[1024]; static char asm_str_buf[1024];
static const char* make_asm_noarch(UID uid) { static const char* make_asm_noarch(UID uid) {
@ -40,6 +31,7 @@ static const char* make_asm(UID uid, S1InstructionPin::ARCH& arch) {
return make_asm_noarch(uid); return make_asm_noarch(uid);
#endif #endif
} }
*/
static tree make_asm_operand(const char* constraint_text, tree operand_tree) { static tree make_asm_operand(const char* constraint_text, tree operand_tree) {
tree constraint_str = build_string(strlen (constraint_text) + 1, constraint_text); tree constraint_str = build_string(strlen (constraint_text) + 1, constraint_text);
@ -52,15 +44,7 @@ static gimple* make_stage1_pin(tree lhs, UID target, std::size_t offset) {
if (!lhs) if (!lhs)
return nullptr; return nullptr;
UID uid = next_pin_uid++; const char* asm_str = s1_ipin_marker; // Asm is inserted at a later stage
S1InstructionPin pin;
pin.target = target;
pin.offset = offset;
const char* asm_str = make_asm(uid, pin.arch);
if (!asm_str)
pinpoint_fatal("make_stage1_pin failed to generate asm string");
tree arg0 = build_int_cst(size_type_node, target); tree arg0 = build_int_cst(size_type_node, target);
tree arg1 = build_int_cst(size_type_node, offset); tree arg1 = build_int_cst(size_type_node, offset);
@ -76,15 +60,9 @@ static gimple* make_stage1_pin(tree lhs, UID target, std::size_t offset) {
if (!new_gasm) if (!new_gasm)
return nullptr; return nullptr;
/* // Asm is intentionally not marked as volatile, gcc may remove it if output is never used
TODO gimple_asm_set_volatile(new_gasm, false);
These asm statements should not actually be volatile.
Instead, a final pass has to detect which have been removed.
Only data about still present ipins should be given to finalizer!
*/
gimple_asm_set_volatile(new_gasm, true);
pins.emplace(uid, pin);
return new_gasm; return new_gasm;
} }

View File

@ -3,17 +3,7 @@
#include <stage0.h> #include <stage0.h>
#include <safe-gimple.h> #include <safe-gimple.h>
struct S1InstructionPin { extern const char* s1_ipin_marker;
enum ARCH {
NONE, X86_64
} arch;
UID target;
std::size_t offset;
static const std::unordered_map<UID, S1InstructionPin>& all();
static void reset();
};
struct asm_offset_pass : gimple_opt_pass { struct asm_offset_pass : gimple_opt_pass {
asm_offset_pass(gcc::context* ctxt); asm_offset_pass(gcc::context* ctxt);

View File

@ -0,0 +1,2 @@
target_sources(spslr_pinpoint PRIVATE rtl_pin_lower_pass.cpp)
target_include_directories(spslr_pinpoint PRIVATE .)

View File

@ -0,0 +1,247 @@
#include <stage2.h>
#include <stage1.h>
#include <pinpoint_config.h>
#include <pinpoint_error.h>
#include <safe-rtl.h>
#include <unordered_map>
#include <string>
#include <cstring>
#include <cstdio>
static UID next_stage2_pin_uid = 0;
static std::unordered_map<UID, S2InstructionPin> pins;
const std::unordered_map<UID, S2InstructionPin>& s2_pins() {
return pins;
}
void s2_pins_reset() {
pins.clear();
next_stage2_pin_uid = 0;
}
UID s2_pin_allocate(const S2InstructionPin& pin) {
UID uid = next_stage2_pin_uid++;
pins.emplace(uid, pin);
return uid;
}
static bool read_marker_inputs(rtx src, UID& target, std::size_t& offset) {
rtx in0 = ASM_OPERANDS_INPUT(src, 0);
rtx in1 = ASM_OPERANDS_INPUT(src, 1);
if (!CONST_INT_P(in0) || !CONST_INT_P(in1))
return false;
target = (UID) INTVAL(in0);
offset = (std::size_t) INTVAL(in1);
return true;
}
static bool extract_set_from_pat(rtx pat, rtx& set_out) {
if (!pat)
return false;
if (GET_CODE(pat) == SET) {
set_out = pat;
return true;
}
if (GET_CODE(pat) == PARALLEL) {
int len = XVECLEN(pat, 0);
for (int i = 0; i < len; ++i) {
rtx elem = XVECEXP(pat, 0, i);
if (elem && GET_CODE(elem) == SET) {
set_out = elem;
return true;
}
}
}
return false;
}
static bool get_regno_from_dest(rtx dest, unsigned& regno) {
if (!dest)
return false;
if (REG_P(dest)) {
regno = REGNO(dest);
return true;
}
if (GET_CODE(dest) == SUBREG) {
rtx inner = SUBREG_REG(dest);
if (inner && REG_P(inner)) {
regno = REGNO(inner);
return true;
}
}
return false;
}
static bool is_stage1_marker_insn(
rtx_insn* insn,
rtx& set_rtx,
rtx& asm_src,
UID& target,
std::size_t& offset,
unsigned& regno
) {
if (!insn || !INSN_P(insn))
return false;
rtx pat = PATTERN(insn);
if (!extract_set_from_pat(pat, set_rtx))
return false;
rtx dest = SET_DEST(set_rtx);
rtx src = SET_SRC(set_rtx);
if (!src || GET_CODE(src) != ASM_OPERANDS)
return false;
const char* templ = ASM_OPERANDS_TEMPLATE(src);
if (!templ)
return false;
/* safer than exact strcmp */
if (!std::strstr(templ, s1_ipin_marker))
return false;
if (!read_marker_inputs(src, target, offset))
return false;
if (!get_regno_from_dest(dest, regno))
return false;
asm_src = src;
return true;
}
static const pass_data rtl_pin_lower_pass_data = {
RTL_PASS, /* type */
"spslr_rtl_pin_lower", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
PROP_rtl, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0 /* todo_flags_finish */
};
struct EncodedReg {
unsigned rex;
unsigned modrm;
};
/*
* Maps the final hard register to:
* mov r/m64, imm32 => REX.W + C7 /0 id
* with mod=11 and r/m = selected GPR
*
* This uses reg_names[] because it is easy to keep backend-local and
* avoids depending on x86 internal enum values here.
*
* If your GCC build uses slightly different names, adjust the table.
*/
static bool x86_64_encode_mov_imm32_to_reg(unsigned regno, EncodedReg& out) {
if (!HARD_REGISTER_NUM_P(regno))
return false;
const char* name = reg_names[regno];
if (!name)
return false;
struct RegMapEntry {
const char* name;
unsigned rex;
unsigned rm;
};
static const RegMapEntry regmap[] = {
{ "ax", 0x48, 0 }, { "cx", 0x48, 1 }, { "dx", 0x48, 2 }, { "bx", 0x48, 3 },
{ "sp", 0x48, 4 }, { "bp", 0x48, 5 }, { "si", 0x48, 6 }, { "di", 0x48, 7 },
{ "r8", 0x49, 0 }, { "r9", 0x49, 1 }, { "r10", 0x49, 2 }, { "r11", 0x49, 3 },
{ "r12", 0x49, 4 }, { "r13", 0x49, 5 }, { "r14", 0x49, 6 }, { "r15", 0x49, 7 },
};
for (const auto& e : regmap) {
if (std::strcmp(name, e.name) == 0) {
out.rex = e.rex;
out.modrm = 0xC0 | e.rm; /* mod=11, /0, rm=e.rm */
return true;
}
}
return false;
}
static std::string make_final_x86_64_asm(UID pin_uid, const EncodedReg& enc, std::size_t imm) {
char buf[256];
std::snprintf(
buf, sizeof(buf),
".byte 0x%02x, 0xC7, 0x%02x\n"
SPSLR_PINPOINT_STAGE2_PIN "%lu:\n"
".long %zu",
enc.rex,
enc.modrm,
static_cast<unsigned long>(pin_uid),
imm
);
return std::string(buf);
}
static bool lower_stage1_marker_insn(rtx_insn* insn) {
rtx set_rtx = nullptr;
rtx asm_src = nullptr;
UID target = 0;
std::size_t offset = 0;
unsigned regno = 0;
if (!is_stage1_marker_insn(insn, set_rtx, asm_src, target, offset, regno))
return false;
EncodedReg enc {};
if (!x86_64_encode_mov_imm32_to_reg(regno, enc)) {
pinpoint_fatal("stage2: unsupported hard register for ipin lowering: regno=%u", (unsigned)regno);
return false;
}
S2InstructionPin pin;
pin.target = target;
pin.offset = offset;
pin.imm_size = 4;
UID pin_uid = s2_pin_allocate(pin);
auto it = pins.find(pin_uid);
if (it == pins.end())
pinpoint_fatal("stage2: internal error after s2_pin_allocate");
it->second.symbol = std::string(SPSLR_PINPOINT_STAGE2_PIN) + std::to_string(pin_uid);
std::string final_asm = make_final_x86_64_asm(pin_uid, enc, offset);
ASM_OPERANDS_TEMPLATE(asm_src) = ggc_strdup(final_asm.c_str());
return true;
}
rtl_pin_lower_pass::rtl_pin_lower_pass(gcc::context* ctxt)
: rtl_opt_pass(rtl_pin_lower_pass_data, ctxt) {}
unsigned int rtl_pin_lower_pass::execute(function* fn) {
(void) fn;
for (rtx_insn* insn = get_insns(); insn; insn = NEXT_INSN(insn)) {
(void) lower_stage1_marker_insn(insn);
}
return 0;
}

22
pinpoint/stage2/stage2.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <stage0.h>
#include <safe-rtl.h>
#include <unordered_map>
#include <string>
struct S2InstructionPin {
UID target;
std::size_t offset;
std::size_t imm_size;
std::string symbol;
};
const std::unordered_map<UID, S2InstructionPin>& s2_pins();
void s2_pins_reset();
UID s2_pin_allocate(const S2InstructionPin& pin);
struct rtl_pin_lower_pass : rtl_opt_pass {
rtl_pin_lower_pass(gcc::context* ctxt);
unsigned int execute(function* fn) override;
};

9
plan.txt Normal file
View File

@ -0,0 +1,9 @@
Make labels globally unique
- Use hash of object file path to identify CU
Collect alignment data on struct members
Fix bit fields and dynamic size fields (at end of structs) in place
Move patcher generation to pre-link stage
- Aggregate meta data files
- Generate patcher object file than links against symbols