From bba1f15a97ef77315947273b33a276ceb9695892 Mon Sep 17 00:00:00 2001 From: York Jasper Niebuhr Date: Sun, 19 Oct 2025 23:26:09 +0200 Subject: [PATCH] Beginnings of pinpoint plugin --- playground/spslr_pinpoint.cpp | 343 ++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 playground/spslr_pinpoint.cpp diff --git a/playground/spslr_pinpoint.cpp b/playground/spslr_pinpoint.cpp new file mode 100644 index 0000000..2023087 --- /dev/null +++ b/playground/spslr_pinpoint.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +int plugin_is_GPL_compatible; + +#define SPSLR_OFFSETOF "__spslr_offsetof" + +// Recognize __attribute__((spslr)) on structures and track their layout + +struct Member { + using OFF = unsigned long; + using SIZE = unsigned long; + + static constexpr int FLAG_DANGERZONE = 1; + + OFF offset = 0; + SIZE size = 0; + int flags = 0; +}; + +struct Target { + using UID = unsigned long; + + UID uid; + std::map members; + + void log_member(const Member& member); + const Member* get_member(Member::OFF offset) const; +}; + +struct TargetTree { + tree t; + Target::UID uid; +}; + +static Target::UID next_target_uid = 0; +static std::unordered_map targets; +static std::list target_trees; + +static std::unordered_set target_markings; + +static bool find_target_log(tree t, Target::UID& uid) { + for (const TargetTree& tt : target_trees) { + if (lang_hooks.types_compatible_p(tt.t, t)) { + uid = tt.uid; + return true; + } + } + + return false; +} + +void Target::log_member(const Member& member) { + Member tmp_member; + tmp_member.offset = member.offset; + tmp_member.size = (member.size == 0 ? 1 : member.size); + tmp_member.flags = (member.size == 0 ? Member::FLAG_DANGERZONE : 0) | member.flags; + + // Overlaps are dangerous -> remove and integrate into member + auto overlap_end = members.lower_bound(tmp_member.offset + tmp_member.size); + for (auto it = std::make_reverse_iterator(overlap_end); it != members.rend();) { + const Member& existing_member = it->second; + + if (existing_member.offset + existing_member.size <= tmp_member.offset) + break; + + Member::OFF combined_end = std::max(tmp_member.offset + tmp_member.size, + existing_member.offset + existing_member.size); + + Member::OFF combined_offset = std::min(tmp_member.offset, existing_member.offset); + Member::SIZE combined_size = combined_end - combined_offset; + + tmp_member.flags |= (existing_member.flags | Member::FLAG_DANGERZONE); + tmp_member.offset = combined_offset; + tmp_member.size = combined_size; + + // Erase overlapping member + auto tmp_forward = std::prev(it.base()); + tmp_forward = members.erase(tmp_forward); + it = std::make_reverse_iterator(tmp_forward); + } + + members.emplace(tmp_member.offset, tmp_member); +} + +const Member* Target::get_member(Member::OFF offset) const { + auto it = members.find(offset); + if (it == members.end()) + return nullptr; + + return &it->second; +} + +static void log_target(tree node) { + if (TREE_CODE(node) != RECORD_TYPE) + return; + + auto marking = target_markings.find(node); + if (marking == target_markings.end()) + return; + + Target::UID existing_target; + if (find_target_log(node, existing_target)) + return; + + Target::UID tuid = next_target_uid++; + + TargetTree new_tt; + new_tt.t = node; + new_tt.uid = tuid; + + target_trees.push_back(new_tt); + targets.emplace(tuid, Target{}); + + Target& target = targets.at(tuid); + target.uid = tuid; + + // Log all members + for (tree field = TYPE_FIELDS(node); field; field = DECL_CHAIN(field)) { + if (TREE_CODE(field) != FIELD_DECL) + continue; + + HOST_WIDE_INT field_byte_offset = 0; + if (TREE_CODE(DECL_FIELD_OFFSET(field)) == INTEGER_CST) + field_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field)); + + HOST_WIDE_INT field_bit_offset = 0; + if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field)) == INTEGER_CST) + field_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field)); + + HOST_WIDE_INT field_bit_offset_bytes = field_bit_offset / 8; + field_byte_offset += field_bit_offset_bytes; + field_bit_offset -= field_bit_offset_bytes * 8; + + HOST_WIDE_INT field_bit_size = 0; + if (TREE_CODE(DECL_SIZE(field)) == INTEGER_CST) + field_bit_size = tree_to_uhwi(DECL_SIZE(field)); + + bool is_bitfield = (DECL_BIT_FIELD_TYPE(field) != NULL_TREE); + bool is_multibyte = (field_bit_size % 8 == 0 && field_bit_offset == 0); + bool is_dangerous = (is_bitfield || !is_multibyte); + + HOST_WIDE_INT field_offset_bit_size = field_bit_offset + field_bit_size; + HOST_WIDE_INT effective_field_size = field_offset_bit_size / 8; + if (field_offset_bit_size % 8 != 0) + effective_field_size += 1; + + Member member; + member.offset = (Member::OFF)field_byte_offset; + member.size = (Member::SIZE)effective_field_size; + member.flags = (is_dangerous ? Member::FLAG_DANGERZONE : 0); + + target.log_member(member); + } +} + +static tree log_target_attribute(tree* node, tree name, tree args, int flags, bool* no_add_attrs) { + if (!node) + return NULL_TREE; + + if (TREE_CODE(*node) != RECORD_TYPE) + return NULL_TREE; + + tree type_main_variant = TYPE_MAIN_VARIANT(*node); + if (!type_main_variant) + return NULL_TREE; + + target_markings.insert(type_main_variant); + return NULL_TREE; +} + +static struct attribute_spec spslr_attribute = { + "spslr", 0, 0, false, false, false, false, log_target_attribute, NULL +}; + +void on_register_attributes(void* event_data, void* data) { + register_attribute(&spslr_attribute); +} + +static void on_type_complete(void* event_data, void* user_data) { + tree type = (tree)event_data; + log_target(TYPE_MAIN_VARIANT(type)); +} + +// Early hook to make COMPONENT_REF nodes survice front end + +struct SPSLROffsetofCallData { + using UID = unsigned long; + + UID uid; + Target::UID target; + Member::OFF member; +}; + +static tree spslr_offsetof_decl = NULL_TREE; +static SPSLROffsetofCallData::UID spslr_offsetof_next_uid = 0; +static std::unordered_map spslr_offsetof_calls; + +static bool require_spslr_offsetof_decl() { + if (spslr_offsetof_decl) + return true; + + tree param_types = tree_cons(NULL_TREE, long_unsigned_type_node, NULL_TREE); + tree fntype = build_function_type(sizetype, param_types); + + spslr_offsetof_decl = build_fn_decl(SPSLR_OFFSETOF, fntype); + if (!spslr_offsetof_decl) + return false; + + DECL_EXTERNAL (spslr_offsetof_decl) = 1; + TREE_PUBLIC (spslr_offsetof_decl) = 1; + DECL_ARTIFICIAL (spslr_offsetof_decl) = 1; + + // Prevent VOP problems later when removing calls + // Explanation -> VOPs mark memory side-effects, which these calls have none of anyways + DECL_PURE_P(spslr_offsetof_decl) = 1; + DECL_IS_NOVOPS(spslr_offsetof_decl) = 1; + + return true; +} + +static bool is_relevant_offsetof(tree ref, Target::UID& tuid, Member::OFF& moff) { + if (!ref || TREE_CODE(ref) != COMPONENT_REF) + return false; + + tree base = TREE_OPERAND(ref, 0); + if (!base) + return false; + + tree base_type = TREE_TYPE(base); + if (!base_type) + return false; + + if (!find_target_log(base_type, tuid)) + return false; + + auto target_it = targets.find(tuid); + if (target_it == targets.end()) + return false; + + const Target& t = target_it->second; + + tree field = TREE_OPERAND(ref, 1); + + HOST_WIDE_INT field_byte_offset = 0; + if (TREE_CODE(DECL_FIELD_OFFSET(field)) == INTEGER_CST) + field_byte_offset = tree_to_uhwi(DECL_FIELD_OFFSET(field)); + + HOST_WIDE_INT field_bit_offset = 0; + if (TREE_CODE(DECL_FIELD_BIT_OFFSET(field)) == INTEGER_CST) + field_bit_offset = tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field)); + + Member::OFF effective_field_offset = (field_byte_offset + (field_bit_offset / 8)); + moff = effective_field_offset; + + const Member* m = t.get_member(effective_field_offset); + if (!m) + return false; + + return !(m->flags & Member::FLAG_DANGERZONE); +} + +static tree instrument_offsetof_maybe(tree ref) { + SPSLROffsetofCallData call_data; + if (!is_relevant_offsetof(ref, call_data.target, call_data.member)) + return NULL_TREE; + + location_t loc = EXPR_LOCATION(ref); + tree result_type = TREE_TYPE(ref); + + if (!require_spslr_offsetof_decl()) { + std::cerr << "spslr_pinpoint -> failed to instrument COMPONENT_REF (" << SPSLR_OFFSETOF << " unavailable)" << std::endl; + return NULL_TREE; + } + + call_data.uid = spslr_offsetof_next_uid++; + + spslr_offsetof_calls.emplace(call_data.uid, call_data); + + tree call_uid_tree = build_int_cst(long_unsigned_type_node, call_data.uid); + tree call_tree = build_call_expr_loc(loc, spslr_offsetof_decl, 1, call_uid_tree); + + // Get base as char* + tree base = TREE_OPERAND(ref, 0); + tree base_addr; + + if (TREE_CODE(base) == ADDR_EXPR) { + // base_addr = base; + std::cerr << "spslr_pinpoint -> unexpected ADDR_EXPR as COMPONENT_REF base!" << std::endl; + return NULL_TREE; + } else { + base_addr = build_fold_addr_expr(base); + } + + tree char_ptr_type = build_pointer_type(char_type_node); + tree base_char_ptr = fold_convert(char_ptr_type, base_addr); + + // Add __spslr_offsetof, cast back to field pointer, then dereference + tree plus = build2_loc(loc, POINTER_PLUS_EXPR, char_ptr_type, base_char_ptr, call_tree); + + tree field_ptr_type = build_pointer_type(result_type); + tree cast_back = build1_loc(loc, NOP_EXPR, field_ptr_type, plus); + + tree new_ref = build1_loc(loc, INDIRECT_REF, result_type, cast_back); + + return new_ref; +} + +static void on_build_component_ref(void* event_data, void* user_data) { + tree* component_ref_node = (tree*)event_data; + if (!component_ref_node) + return; + + tree repl = instrument_offsetof_maybe(*component_ref_node); + if (repl) + *component_ref_node = repl; +} + + +// Hook everything up in plugin_init + +int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) { + if (!plugin_default_version_check(version, &gcc_version)) { + fprintf(stderr, "GCC version mismatch!\n"); + std::cerr << "spslr_pinpoint -> GCC version mismatch" << std::endl; + return 1; + } + + register_callback(plugin_info->base_name, PLUGIN_ATTRIBUTES, on_register_attributes, NULL); + register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, on_type_complete, NULL); + register_callback(plugin_info->base_name, PLUGIN_BUILD_COMPONENT_REF, on_build_component_ref, NULL); + + return 0; +}