Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _ARCH_H
#define _ARCH_H
#include <stdbool.h>
#include <linux/list.h>
#include <objtool/objtool.h>
#include <objtool/cfi.h>
enum insn_type {
INSN_JUMP_CONDITIONAL,
INSN_JUMP_UNCONDITIONAL,
INSN_JUMP_DYNAMIC,
INSN_JUMP_DYNAMIC_CONDITIONAL,
INSN_CALL,
INSN_CALL_DYNAMIC,
INSN_RETURN,
INSN_SYSCALL,
INSN_SYSRET,
INSN_BUG,
INSN_NOP,
INSN_STAC,
INSN_CLAC,
INSN_STD,
INSN_CLD,
INSN_TRAP,
INSN_ENDBR,
INSN_LEA_RIP,
INSN_OTHER,
};
enum op_dest_type {
OP_DEST_REG,
OP_DEST_REG_INDIRECT,
OP_DEST_MEM,
OP_DEST_PUSH,
OP_DEST_PUSHF,
};
struct op_dest {
enum op_dest_type type;
unsigned char reg;
int offset;
};
enum op_src_type {
OP_SRC_REG,
OP_SRC_REG_INDIRECT,
OP_SRC_CONST,
OP_SRC_POP,
OP_SRC_POPF,
OP_SRC_ADD,
OP_SRC_AND,
};
struct op_src {
enum op_src_type type;
unsigned char reg;
int offset;
};
struct stack_op {
struct stack_op *next;
struct op_dest dest;
struct op_src src;
};
struct instruction;
int arch_ftrace_match(const char *name);
void arch_initial_func_cfi_state(struct cfi_init_state *state);
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn);
size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
unsigned char *buf);
bool arch_callee_saved_reg(unsigned char reg);
unsigned long arch_jump_destination(struct instruction *insn);
s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc);
u64 arch_adjusted_addend(struct reloc *reloc);
const char *arch_nop_insn(int len);
const char *arch_ret_insn(int len);
int arch_decode_hint_reg(u8 sp_reg, int *base);
bool arch_is_retpoline(struct symbol *sym);
bool arch_is_rethunk(struct symbol *sym);
bool arch_is_embedded_insn(struct symbol *sym);
int arch_rewrite_retpolines(struct objtool_file *file);
bool arch_pc_relative_reloc(struct reloc *reloc);
bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc);
unsigned int arch_reloc_size(struct reloc *reloc);
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table);
extern const char *arch_reg_name[CFI_NUM_REGS];
#ifdef DISAS
#include <bfd.h>
#include <dis-asm.h>
int arch_disas_info_init(struct disassemble_info *dinfo);
#endif /* DISAS */
#endif /* _ARCH_H */
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _BUILTIN_H
#define _BUILTIN_H
#include <subcmd/parse-options.h>
struct opts {
/* actions: */
bool checksum;
const char *disas;
bool dump_orc;
bool hack_jump_label;
bool hack_noinstr;
bool hack_skylake;
bool ibt;
bool mcount;
bool noabs;
bool noinstr;
bool orc;
int prefix;
bool retpoline;
bool rethunk;
bool unret;
bool sls;
bool stackval;
bool static_call;
bool uaccess;
/* options: */
bool backtrace;
bool backup;
bool cfi;
const char *debug_checksum;
bool dryrun;
bool fineibt;
bool link;
bool mnop;
bool module;
bool no_unreachable;
const char *output;
bool sec_address;
bool stats;
const char *trace;
bool verbose;
bool werror;
bool wide;
};
extern struct opts opts;
int cmd_parse_options(int argc, const char **argv, const char * const usage[]);
int objtool_run(int argc, const char **argv);
int make_backup(void);
int cmd_klp(int argc, const char **argv);
#endif /* _BUILTIN_H */
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _OBJTOOL_CFI_H
#define _OBJTOOL_CFI_H
#include <arch/cfi_regs.h>
#include <linux/list.h>
#define CFI_UNDEFINED -1
#define CFI_CFA -2
#define CFI_SP_INDIRECT -3
#define CFI_BP_INDIRECT -4
struct cfi_reg {
int base;
int offset;
};
struct cfi_init_state {
struct cfi_reg regs[CFI_NUM_REGS];
struct cfi_reg cfa;
};
struct cfi_state {
struct hlist_node hash; /* must be first, cficmp() */
struct cfi_reg regs[CFI_NUM_REGS];
struct cfi_reg vals[CFI_NUM_REGS];
struct cfi_reg cfa;
int stack_size;
int drap_reg, drap_offset;
unsigned char type;
bool bp_scratch;
bool drap;
bool signal;
bool end;
bool force_undefined;
};
#endif /* _OBJTOOL_CFI_H */
@@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _CHECK_H
#define _CHECK_H
#include <stdbool.h>
#include <objtool/cfi.h>
#include <objtool/arch.h>
struct insn_state {
struct cfi_state cfi;
unsigned int uaccess_stack;
bool uaccess;
bool df;
bool noinstr;
s8 instr;
};
struct alt_group {
/*
* Pointer from a replacement group to the original group. NULL if it
* *is* the original group.
*/
struct alt_group *orig_group;
/* First and last instructions in the group */
struct instruction *first_insn, *last_insn, *nop;
/*
* Byte-offset-addressed len-sized array of pointers to CFI structs.
* This is shared with the other alt_groups in the same alternative.
*/
struct cfi_state **cfi;
bool ignore;
unsigned int feature;
};
enum alternative_type {
ALT_TYPE_INSTRUCTIONS,
ALT_TYPE_JUMP_TABLE,
ALT_TYPE_EX_TABLE,
};
struct alternative {
struct alternative *next;
struct instruction *insn;
enum alternative_type type;
};
#define INSN_CHUNK_BITS 8
#define INSN_CHUNK_SIZE (1 << INSN_CHUNK_BITS)
#define INSN_CHUNK_MAX (INSN_CHUNK_SIZE - 1)
struct instruction {
struct hlist_node hash;
struct list_head call_node;
struct section *sec;
unsigned long offset;
unsigned long immediate;
u8 len;
u8 prev_len;
u8 type;
s8 instr;
u32 idx : INSN_CHUNK_BITS,
immediate_len : 4,
dead_end : 1,
ignore_alts : 1,
hint : 1,
save : 1,
restore : 1,
retpoline_safe : 1,
noendbr : 1,
unret : 1,
visited : 4,
no_reloc : 1,
hole : 1,
fake : 1,
trace : 1;
/* 4 bit hole */
struct alt_group *alt_group;
struct instruction *jump_dest;
struct instruction *first_jump_src;
union {
struct symbol *_call_dest;
struct {
struct reloc *_jump_table;
unsigned long _jump_table_size;
};
};
struct alternative *alts;
struct symbol *_sym;
struct stack_op *stack_ops;
struct cfi_state *cfi;
};
/*
* Return the symbol associated with an instruction. For alternative
* replacements, return the symbol of the original code being replaced rather
* than NULL. insn->_sym reflects the actual location in the ELF file.
*/
static inline struct symbol *insn_sym(struct instruction *insn)
{
struct symbol *sym = insn->_sym;
if ((!sym || !is_func_sym(sym)) &&
insn->alt_group && insn->alt_group->orig_group)
sym = insn->alt_group->orig_group->first_insn->_sym;
return sym;
}
static inline struct symbol *insn_func(struct instruction *insn)
{
struct symbol *sym = insn_sym(insn);
if (sym && sym->type != STT_FUNC)
sym = NULL;
return sym;
}
#define VISITED_BRANCH 0x01
#define VISITED_BRANCH_UACCESS 0x02
#define VISITED_BRANCH_MASK 0x03
#define VISITED_UNRET 0x04
static inline bool is_static_jump(struct instruction *insn)
{
return insn->type == INSN_JUMP_CONDITIONAL ||
insn->type == INSN_JUMP_UNCONDITIONAL;
}
static inline bool is_dynamic_jump(struct instruction *insn)
{
return insn->type == INSN_JUMP_DYNAMIC ||
insn->type == INSN_JUMP_DYNAMIC_CONDITIONAL;
}
static inline bool is_jump(struct instruction *insn)
{
return is_static_jump(insn) || is_dynamic_jump(insn);
}
static inline struct symbol *insn_call_dest(struct instruction *insn)
{
if (insn->type == INSN_JUMP_DYNAMIC ||
insn->type == INSN_CALL_DYNAMIC)
return NULL;
return insn->_call_dest;
}
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset);
struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *insn);
struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn);
#define func_for_each_insn(file, func, insn) \
for (insn = find_insn(file, func->sec, func->offset); \
insn; \
insn = next_insn_same_func(file, insn))
#define sec_for_each_insn(file, _sec, insn) \
for (insn = find_insn(file, _sec, 0); \
insn && insn->sec == _sec; \
insn = next_insn_same_sec(file, insn))
#define sym_for_each_insn(file, sym, insn) \
for (insn = find_insn(file, sym->sec, sym->offset); \
insn && insn->offset < sym->offset + sym->len; \
insn = next_insn_same_sec(file, insn))
struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn);
int decode_file(struct objtool_file *file);
void free_insns(struct objtool_file *file);
const char *objtool_disas_insn(struct instruction *insn);
extern size_t sym_name_max_len;
extern struct disas_context *objtool_disas_ctx;
int pv_ops_idx_off(const char *symname);
#endif /* _CHECK_H */
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_CHECKSUM_H
#define _OBJTOOL_CHECKSUM_H
#include <objtool/elf.h>
#ifdef BUILD_KLP
static inline void checksum_init(struct symbol *sym)
{
if (sym && !sym->csum.state) {
sym->csum.state = XXH3_createState();
XXH3_64bits_reset(sym->csum.state);
}
}
static inline void __checksum_update(struct symbol *sym, const void *data,
size_t size)
{
XXH3_64bits_update(sym->csum.state, data, size);
}
static inline void __checksum_update_insn(struct symbol *sym,
struct instruction *insn,
const void *data, size_t size)
{
__checksum_update(sym, data, size);
dbg_checksum_insn(sym, insn, XXH3_64bits_digest(sym->csum.state));
}
static inline void __checksum_update_object(struct symbol *sym,
unsigned long offset,
const char *what, const void *data,
size_t size)
{
__checksum_update(sym, &offset, sizeof(offset));
__checksum_update(sym, data, size);
dbg_checksum_object(sym, offset, what, XXH3_64bits_digest(sym->csum.state));
}
static inline void checksum_finish(struct symbol *sym)
{
if (sym && sym->csum.state) {
sym->csum.checksum = XXH3_64bits_digest(sym->csum.state);
XXH3_freeState(sym->csum.state);
sym->csum.state = NULL;
}
}
int calculate_checksums(struct objtool_file *file);
int create_sym_checksum_section(struct objtool_file *file);
#else /* !BUILD_KLP */
static inline int calculate_checksums(struct objtool_file *file) { return -ENOSYS; }
static inline int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; }
#endif /* !BUILD_KLP */
#endif /* _OBJTOOL_CHECKSUM_H */
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _OBJTOOL_CHECKSUM_TYPES_H
#define _OBJTOOL_CHECKSUM_TYPES_H
struct sym_checksum {
u64 addr;
u64 checksum;
};
#ifdef BUILD_KLP
#include <xxhash.h>
struct checksum {
XXH3_state_t *state;
XXH64_hash_t checksum;
};
#else /* !BUILD_KLP */
struct checksum {};
#endif /* !BUILD_KLP */
#endif /* _OBJTOOL_CHECKSUM_TYPES_H */
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2025, Oracle and/or its affiliates.
*/
#ifndef _DISAS_H
#define _DISAS_H
struct alternative;
struct disas_context;
struct disassemble_info;
#ifdef DISAS
struct disas_context *disas_context_create(struct objtool_file *file);
void disas_context_destroy(struct disas_context *dctx);
void disas_warned_funcs(struct disas_context *dctx);
void disas_funcs(struct disas_context *dctx);
int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options);
size_t disas_insn(struct disas_context *dctx, struct instruction *insn);
char *disas_result(struct disas_context *dctx);
void disas_print_info(FILE *stream, struct instruction *insn, int depth,
const char *format, ...);
void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...);
char *disas_alt_name(struct alternative *alt);
const char *disas_alt_type_name(struct instruction *insn);
#else /* DISAS */
#include <objtool/warn.h>
static inline struct disas_context *disas_context_create(struct objtool_file *file)
{
WARN("Rebuild with libopcodes for disassembly support");
return NULL;
}
static inline void disas_context_destroy(struct disas_context *dctx) {}
static inline void disas_warned_funcs(struct disas_context *dctx) {}
static inline void disas_funcs(struct disas_context *dctx) {}
static inline int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options)
{
return -1;
}
static inline size_t disas_insn(struct disas_context *dctx,
struct instruction *insn)
{
return -1;
}
static inline char *disas_result(struct disas_context *dctx)
{
return NULL;
}
static inline void disas_print_info(FILE *stream, struct instruction *insn,
int depth, const char *format, ...) {}
static inline void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...) {}
static inline char *disas_alt_name(struct alternative *alt)
{
return NULL;
}
static inline const char *disas_alt_type_name(struct instruction *insn)
{
return NULL;
}
#endif /* DISAS */
#endif /* _DISAS_H */
@@ -0,0 +1,582 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _OBJTOOL_ELF_H
#define _OBJTOOL_ELF_H
#include <stdio.h>
#include <gelf.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <linux/rbtree.h>
#include <linux/jhash.h>
#include <objtool/endianness.h>
#include <objtool/checksum_types.h>
#include <arch/elf.h>
#define SEC_NAME_LEN 1024
#define SYM_NAME_LEN 512
static inline u32 str_hash(const char *str)
{
return jhash(str, strlen(str), 0);
}
u32 str_hash_demangled(const char *str);
#define bswap_if_needed(elf, val) __bswap_if_needed(&elf->ehdr, val)
#ifdef LIBELF_USE_DEPRECATED
# define elf_getshdrnum elf_getshnum
# define elf_getshdrstrndx elf_getshstrndx
#endif
/*
* Fallback for systems without this "read, mmaping if possible" cmd.
*/
#ifndef ELF_C_READ_MMAP
#define ELF_C_READ_MMAP ELF_C_READ
#endif
struct elf_hash_node {
struct elf_hash_node *next;
};
struct section {
struct list_head list;
struct elf_hash_node hash;
struct elf_hash_node name_hash;
GElf_Shdr sh;
struct rb_root_cached symbol_tree;
struct list_head symbol_list;
struct section *base, *rsec;
struct symbol *sym;
Elf_Data *data;
const char *name;
int idx;
bool _changed, text, rodata, noinstr, init, truncate;
struct reloc *relocs;
unsigned long nr_alloc_relocs;
struct section *twin;
};
struct symbol {
struct list_head list;
struct list_head global_list;
struct rb_node node;
struct elf_hash_node hash;
struct elf_hash_node name_hash;
GElf_Sym sym;
struct section *sec;
const char *name, *demangled_name;
unsigned int idx, len;
unsigned long offset;
unsigned long __subtree_last;
struct symbol *pfunc, *cfunc, *alias, *file;
unsigned char bind, type;
u8 uaccess_safe : 1;
u8 static_call_tramp : 1;
u8 retpoline_thunk : 1;
u8 return_thunk : 1;
u8 fentry : 1;
u8 profiling_func : 1;
u8 warned : 1;
u8 embedded_insn : 1;
u8 local_label : 1;
u8 frame_pointer : 1;
u8 ignore : 1;
u8 nocfi : 1;
u8 cold : 1;
u8 prefix : 1;
u8 debug_checksum : 1;
u8 changed : 1;
u8 included : 1;
u8 klp : 1;
u8 dont_correlate : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
struct checksum csum;
struct symbol *twin, *clone;
};
struct reloc {
struct elf_hash_node hash;
struct section *sec;
struct symbol *sym;
unsigned long _sym_next_reloc;
};
struct elf {
Elf *elf;
GElf_Ehdr ehdr;
int fd;
bool changed;
const char *name, *tmp_name;
unsigned int num_files;
struct list_head sections;
struct list_head symbols;
unsigned long num_relocs;
int symbol_bits;
int symbol_name_bits;
int section_bits;
int section_name_bits;
int reloc_bits;
struct elf_hash_node **symbol_hash;
struct elf_hash_node **symbol_name_hash;
struct elf_hash_node **section_hash;
struct elf_hash_node **section_name_hash;
struct elf_hash_node **reloc_hash;
struct section *section_data;
struct symbol *symbol_data;
};
#define __elf_table(elf, name) ((elf)->name##_hash)
#define __elf_bits(elf, name) ((elf)->name##_bits)
#define __elf_table_entry(elf, name, key) \
__elf_table(elf, name)[hash_min(key, __elf_bits(elf, name))]
#define elf_list_entry(ptr, type, member) \
({ \
typeof(ptr) __ptr = (ptr); \
__ptr ? container_of(__ptr, type, member) : NULL; \
})
#define elf_hash_for_each_possible(elf, name, obj, member, key) \
for (obj = elf_list_entry(__elf_table_entry(elf, name, key), typeof(*obj), member); \
obj; \
obj = elf_list_entry(obj->member.next, typeof(*(obj)), member))
struct elf *elf_open_read(const char *name, int flags);
struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name);
struct section *elf_create_section(struct elf *elf, const char *name,
size_t size, size_t entsize,
unsigned int type, unsigned int align,
unsigned int flags);
struct section *elf_create_section_pair(struct elf *elf, const char *name,
size_t entsize, unsigned int nr,
unsigned int reloc_nr);
struct section *elf_create_rela_section(struct elf *elf, struct section *sec,
unsigned int reloc_nr);
struct symbol *elf_create_symbol(struct elf *elf, const char *name,
struct section *sec, unsigned int bind,
unsigned int type, unsigned long offset,
size_t size);
struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec);
void *elf_add_data(struct elf *elf, struct section *sec, const void *data,
size_t size);
unsigned int elf_add_string(struct elf *elf, struct section *strtab, const char *str);
struct reloc *elf_create_reloc(struct elf *elf, struct section *sec,
unsigned long offset, struct symbol *sym,
s64 addend, unsigned int type);
struct reloc *elf_init_reloc(struct elf *elf, struct section *rsec,
unsigned int reloc_idx, unsigned long offset,
struct symbol *sym, s64 addend, unsigned int type);
struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec,
unsigned long offset,
unsigned int reloc_idx,
struct section *insn_sec,
unsigned long insn_off);
struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec,
unsigned long offset,
unsigned int reloc_idx,
struct symbol *sym,
s64 addend);
int elf_write_symbol(struct elf *elf, struct symbol *sym);
int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset,
unsigned int len, const char *insn);
int elf_write(struct elf *elf);
int elf_close(struct elf *elf);
struct section *find_section_by_name(const struct elf *elf, const char *name);
struct symbol *find_func_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_name(const struct elf *elf, const char *name);
struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name);
struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset);
struct symbol *find_symbol_containing_inclusive(const struct section *sec, unsigned long offset);
int find_symbol_hole_containing(const struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
unsigned long offset, unsigned int len);
struct symbol *find_func_containing(struct section *sec, unsigned long offset);
/*
* Try to see if it's a whole archive (vmlinux.o or module).
*
* Note this will miss the case where a module only has one source file.
*/
static inline bool has_multiple_files(struct elf *elf)
{
return elf->num_files > 1;
}
static inline size_t elf_addr_size(struct elf *elf)
{
return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
}
static inline size_t elf_rela_size(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? sizeof(Elf32_Rela) : sizeof(Elf64_Rela);
}
static inline unsigned int elf_data_rela_type(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? R_DATA32 : R_DATA64;
}
static inline unsigned int elf_text_rela_type(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? R_TEXT32 : R_TEXT64;
}
static inline bool is_undef_sym(struct symbol *sym)
{
return !sym->sec->idx;
}
static inline bool is_null_sym(struct symbol *sym)
{
return !sym->idx;
}
static inline bool is_sec_sym(struct symbol *sym)
{
return sym->type == STT_SECTION;
}
static inline bool is_object_sym(struct symbol *sym)
{
return sym->type == STT_OBJECT;
}
static inline bool is_func_sym(struct symbol *sym)
{
return sym->type == STT_FUNC;
}
static inline bool is_file_sym(struct symbol *sym)
{
return sym->type == STT_FILE;
}
static inline bool is_notype_sym(struct symbol *sym)
{
return sym->type == STT_NOTYPE;
}
static inline bool is_global_sym(struct symbol *sym)
{
return sym->bind == STB_GLOBAL;
}
static inline bool is_weak_sym(struct symbol *sym)
{
return sym->bind == STB_WEAK;
}
static inline bool is_local_sym(struct symbol *sym)
{
return sym->bind == STB_LOCAL;
}
static inline bool is_alias_sym(struct symbol *sym)
{
return sym->alias != sym;
}
static inline bool is_prefix_func(struct symbol *sym)
{
return sym->prefix;
}
static inline bool is_cold_func(struct symbol *sym)
{
return sym->cold;
}
static inline bool is_reloc_sec(struct section *sec)
{
return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL;
}
static inline bool is_string_sec(struct section *sec)
{
return sec->sh.sh_flags & SHF_STRINGS;
}
static inline bool is_text_sec(struct section *sec)
{
return sec->sh.sh_flags & SHF_EXECINSTR;
}
static inline bool is_rodata_sec(struct section *sec)
{
return sec->rodata;
}
static inline bool sec_changed(struct section *sec)
{
return sec->_changed;
}
static inline void mark_sec_changed(struct elf *elf, struct section *sec,
bool changed)
{
sec->_changed = changed;
elf->changed |= changed;
}
static inline unsigned int sec_num_entries(struct section *sec)
{
return sec->sh.sh_size / sec->sh.sh_entsize;
}
static inline unsigned int reloc_idx(struct reloc *reloc)
{
return reloc - reloc->sec->relocs;
}
static inline void *reloc_rel(struct reloc *reloc)
{
struct section *rsec = reloc->sec;
return rsec->data->d_buf + (reloc_idx(reloc) * rsec->sh.sh_entsize);
}
static inline bool is_32bit_reloc(struct reloc *reloc)
{
/*
* Elf32_Rel: 8 bytes
* Elf32_Rela: 12 bytes
* Elf64_Rel: 16 bytes
* Elf64_Rela: 24 bytes
*/
return reloc->sec->sh.sh_entsize < 16;
}
static inline unsigned long sec_size(struct section *sec)
{
return sec->sh.sh_size;
}
#define __get_reloc_field(reloc, field) \
({ \
is_32bit_reloc(reloc) ? \
((Elf32_Rela *)reloc_rel(reloc))->field : \
((Elf64_Rela *)reloc_rel(reloc))->field; \
})
#define __set_reloc_field(reloc, field, val) \
({ \
if (is_32bit_reloc(reloc)) \
((Elf32_Rela *)reloc_rel(reloc))->field = val; \
else \
((Elf64_Rela *)reloc_rel(reloc))->field = val; \
})
static inline u64 reloc_offset(struct reloc *reloc)
{
return __get_reloc_field(reloc, r_offset);
}
static inline void set_reloc_offset(struct elf *elf, struct reloc *reloc, u64 offset)
{
__set_reloc_field(reloc, r_offset, offset);
mark_sec_changed(elf, reloc->sec, true);
}
static inline s64 reloc_addend(struct reloc *reloc)
{
return __get_reloc_field(reloc, r_addend);
}
static inline void set_reloc_addend(struct elf *elf, struct reloc *reloc, s64 addend)
{
__set_reloc_field(reloc, r_addend, addend);
mark_sec_changed(elf, reloc->sec, true);
}
static inline unsigned int reloc_sym(struct reloc *reloc)
{
u64 info = __get_reloc_field(reloc, r_info);
return is_32bit_reloc(reloc) ?
ELF32_R_SYM(info) :
ELF64_R_SYM(info);
}
static inline unsigned int reloc_type(struct reloc *reloc)
{
u64 info = __get_reloc_field(reloc, r_info);
return is_32bit_reloc(reloc) ?
ELF32_R_TYPE(info) :
ELF64_R_TYPE(info);
}
static inline void set_reloc_sym(struct elf *elf, struct reloc *reloc, unsigned int sym)
{
u64 info = is_32bit_reloc(reloc) ?
ELF32_R_INFO(sym, reloc_type(reloc)) :
ELF64_R_INFO(sym, reloc_type(reloc));
__set_reloc_field(reloc, r_info, info);
mark_sec_changed(elf, reloc->sec, true);
}
static inline void set_reloc_type(struct elf *elf, struct reloc *reloc, unsigned int type)
{
u64 info = is_32bit_reloc(reloc) ?
ELF32_R_INFO(reloc_sym(reloc), type) :
ELF64_R_INFO(reloc_sym(reloc), type);
__set_reloc_field(reloc, r_info, info);
mark_sec_changed(elf, reloc->sec, true);
}
static inline unsigned int annotype(struct elf *elf, struct section *sec,
struct reloc *reloc)
{
unsigned int type;
type = *(u32 *)(sec->data->d_buf + (reloc_idx(reloc) * 8) + 4);
return bswap_if_needed(elf, type);
}
#define RELOC_JUMP_TABLE_BIT 1UL
/* Does reloc mark the beginning of a jump table? */
static inline bool is_jump_table(struct reloc *reloc)
{
return reloc->_sym_next_reloc & RELOC_JUMP_TABLE_BIT;
}
static inline void set_jump_table(struct reloc *reloc)
{
reloc->_sym_next_reloc |= RELOC_JUMP_TABLE_BIT;
}
static inline struct reloc *sym_next_reloc(struct reloc *reloc)
{
return (struct reloc *)(reloc->_sym_next_reloc & ~RELOC_JUMP_TABLE_BIT);
}
static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)
{
unsigned long bit = reloc->_sym_next_reloc & RELOC_JUMP_TABLE_BIT;
reloc->_sym_next_reloc = (unsigned long)next | bit;
}
#define for_each_sec(elf, sec) \
list_for_each_entry(sec, &elf->sections, list)
#define sec_for_each_sym(sec, sym) \
list_for_each_entry(sym, &sec->symbol_list, list)
#define sec_prev_sym(sym) \
sym->sec && sym->list.prev != &sym->sec->symbol_list ? \
list_prev_entry(sym, list) : NULL
#define for_each_sym(elf, sym) \
list_for_each_entry(sym, &elf->symbols, global_list)
#define for_each_sym_continue(elf, sym) \
list_for_each_entry_continue(sym, &elf->symbols, global_list)
#define for_each_sym_by_name(elf, _name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash_demangled(_name)) \
if (strcmp(sym->name, _name)) {} else
#define for_each_sym_by_demangled_name(elf, name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash(name)) \
if (strcmp(sym->demangled_name, name)) {} else
#define rsec_next_reloc(rsec, reloc) \
reloc_idx(reloc) < sec_num_entries(rsec) - 1 ? reloc + 1 : NULL
#define for_each_reloc(rsec, reloc) \
for (reloc = rsec->relocs; reloc; reloc = rsec_next_reloc(rsec, reloc))
#define for_each_reloc_from(rsec, reloc) \
for (; reloc; reloc = rsec_next_reloc(rsec, reloc))
#define for_each_reloc_continue(rsec, reloc) \
for (reloc = rsec_next_reloc(rsec, reloc); reloc; \
reloc = rsec_next_reloc(rsec, reloc))
#define sym_for_each_reloc(elf, sym, reloc) \
for (reloc = find_reloc_by_dest_range(elf, sym->sec, \
sym->offset, sym->len); \
reloc && reloc_offset(reloc) < sym->offset + sym->len; \
reloc = rsec_next_reloc(sym->sec->rsec, reloc))
static inline struct symbol *get_func_prefix(struct symbol *func)
{
struct symbol *prev;
if (!is_func_sym(func) || !func->offset)
return NULL;
prev = find_func_containing(func->sec, func->offset - 1);
if (prev && is_prefix_func(prev))
return prev;
return NULL;
}
#define OFFSET_STRIDE_BITS 4
#define OFFSET_STRIDE (1UL << OFFSET_STRIDE_BITS)
#define OFFSET_STRIDE_MASK (~(OFFSET_STRIDE - 1))
#define for_offset_range(_offset, _start, _end) \
for (_offset = ((_start) & OFFSET_STRIDE_MASK); \
_offset >= ((_start) & OFFSET_STRIDE_MASK) && \
_offset <= ((_end) & OFFSET_STRIDE_MASK); \
_offset += OFFSET_STRIDE)
static inline u32 sec_offset_hash(struct section *sec, unsigned long offset)
{
u32 ol, oh, idx = sec->idx;
offset &= OFFSET_STRIDE_MASK;
ol = offset;
oh = (offset >> 16) >> 16;
__jhash_mix(ol, oh, idx);
return ol;
}
static inline u32 reloc_hash(struct reloc *reloc)
{
return sec_offset_hash(reloc->sec, reloc_offset(reloc));
}
#endif /* _OBJTOOL_ELF_H */
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ENDIANNESS_H
#define _OBJTOOL_ENDIANNESS_H
#include <linux/kernel.h>
#include <endian.h>
/*
* Does a byte swap if target file endianness doesn't match the host, i.e. cross
* compilation for little endian on big endian and vice versa.
* To be used for multi-byte values conversion, which are read from / about
* to be written to a target native endianness ELF file.
*/
static inline bool need_bswap(GElf_Ehdr *ehdr)
{
return (__BYTE_ORDER == __LITTLE_ENDIAN) ^
(ehdr->e_ident[EI_DATA] == ELFDATA2LSB);
}
#define __bswap_if_needed(ehdr, val) \
({ \
__typeof__(val) __ret; \
bool __need_bswap = need_bswap(ehdr); \
switch (sizeof(val)) { \
case 8: \
__ret = __need_bswap ? bswap_64(val) : (val); break; \
case 4: \
__ret = __need_bswap ? bswap_32(val) : (val); break; \
case 2: \
__ret = __need_bswap ? bswap_16(val) : (val); break; \
default: \
BUILD_BUG(); break; \
} \
__ret; \
})
#endif /* _OBJTOOL_ENDIANNESS_H */
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_KLP_H
#define _OBJTOOL_KLP_H
#define SHF_RELA_LIVEPATCH 0x00100000
#define SHN_LIVEPATCH 0xff20
/*
* .init.klp_objects and .init.klp_funcs are created by klp diff and used by the
* patch module init code to build the klp_patch, klp_object and klp_func
* structs needed by the livepatch API.
*/
#define KLP_OBJECTS_SEC ".init.klp_objects"
#define KLP_FUNCS_SEC ".init.klp_funcs"
/*
* __klp_relocs is an intermediate section which are created by klp diff and
* converted into KLP symbols/relas by "objtool klp post-link". This is needed
* to work around the linker, which doesn't preserve SHN_LIVEPATCH or
* SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a
* single PROGBITS section.
*/
#define KLP_RELOCS_SEC "__klp_relocs"
#define KLP_STRINGS_SEC ".rodata.klp.str1.1"
struct klp_reloc {
void *offset;
void *sym;
u32 type;
};
int cmd_klp_checksum(int argc, const char **argv);
int cmd_klp_diff(int argc, const char **argv);
int cmd_klp_post_link(int argc, const char **argv);
#endif /* _OBJTOOL_KLP_H */
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020 Matt Helsley <mhelsley@vmware.com>
*/
#ifndef _OBJTOOL_H
#define _OBJTOOL_H
#include <stdbool.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <objtool/elf.h>
#define __weak __attribute__((weak))
struct pv_state {
bool clean;
struct list_head targets;
};
struct objtool_file {
struct elf *elf;
DECLARE_HASHTABLE(insn_hash, 20);
struct list_head retpoline_call_list;
struct list_head return_thunk_list;
struct list_head static_call_list;
struct list_head mcount_loc_list;
struct list_head endbr_list;
struct list_head call_list;
bool ignore_unreachables, hints, rodata, klp;
unsigned int nr_endbr;
unsigned int nr_endbr_int;
unsigned long jl_short, jl_long;
unsigned long jl_nop_short, jl_nop_long;
struct pv_state *pv_ops;
};
char *top_level_dir(const char *file);
int init_signal_handler(void);
struct objtool_file *objtool_open_read(const char *_objname);
int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
int check(struct objtool_file *file);
int orc_dump(const char *objname);
int orc_create(struct objtool_file *file);
#endif /* _OBJTOOL_H */
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ORC_H
#define _OBJTOOL_ORC_H
#include <objtool/check.h>
int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, struct instruction *insn);
void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i);
int write_orc_entry(struct elf *elf, struct section *orc_sec,
struct section *ip_sec, unsigned int idx,
struct section *insn_sec, unsigned long insn_off,
struct orc_entry *o);
#endif /* _OBJTOOL_ORC_H */
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _SPECIAL_H
#define _SPECIAL_H
#include <stdbool.h>
#include <objtool/check.h>
#include <objtool/elf.h>
#define C_JUMP_TABLE_SECTION ".data.rel.ro.c_jump_table"
struct special_alt {
struct list_head list;
bool group;
bool jump_or_nop;
u8 key_addend;
struct section *orig_sec;
unsigned long orig_off;
struct section *new_sec;
unsigned long new_off;
unsigned int orig_len, new_len, feature; /* group only */
};
int special_get_alts(struct elf *elf, struct list_head *alts);
void arch_handle_alternative(struct special_alt *alt);
bool arch_support_alt_relocation(struct special_alt *special_alt,
struct instruction *insn,
struct reloc *reloc);
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size);
const char *arch_cpu_feature_name(int feature_number);
#endif /* _SPECIAL_H */
@@ -0,0 +1,141 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2025, Oracle and/or its affiliates.
*/
#ifndef _TRACE_H
#define _TRACE_H
#include <objtool/check.h>
#include <objtool/disas.h>
#ifdef DISAS
extern bool trace;
extern int trace_depth;
#define TRACE(fmt, ...) \
({ if (trace) \
fprintf(stderr, fmt, ##__VA_ARGS__); \
})
/*
* Print the instruction address and a message. The instruction
* itself is not printed.
*/
#define TRACE_ADDR(insn, fmt, ...) \
({ \
if (trace) { \
disas_print_info(stderr, insn, trace_depth - 1, \
fmt "\n", ##__VA_ARGS__); \
} \
})
/*
* Print the instruction address, the instruction and a message.
*/
#define TRACE_INSN(insn, fmt, ...) \
({ \
if (trace) { \
disas_print_insn(stderr, objtool_disas_ctx, \
insn, trace_depth - 1, \
fmt, ##__VA_ARGS__); \
fprintf(stderr, "\n"); \
insn->trace = 1; \
} \
})
#define TRACE_INSN_STATE(insn, sprev, snext) \
({ \
if (trace) \
trace_insn_state(insn, sprev, snext); \
})
#define TRACE_ALT_FMT(pfx, fmt) pfx "<%s.%lx> " fmt
#define TRACE_ALT_ARG(insn) disas_alt_type_name(insn), (insn)->offset
#define TRACE_ALT(insn, fmt, ...) \
TRACE_INSN(insn, TRACE_ALT_FMT("", fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_INFO(insn, pfx, fmt, ...) \
TRACE_ADDR(insn, TRACE_ALT_FMT(pfx, fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_INFO_NOADDR(insn, pfx, fmt, ...) \
TRACE_ADDR(NULL, TRACE_ALT_FMT(pfx, fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_BEGIN(insn, alt, alt_name) \
({ \
if (trace) { \
alt_name = disas_alt_name(alt); \
trace_alt_begin(insn, alt, alt_name); \
} \
})
#define TRACE_ALT_END(insn, alt, alt_name) \
({ \
if (trace) { \
trace_alt_end(insn, alt, alt_name); \
free(alt_name); \
} \
})
static inline void trace_enable(void)
{
trace = true;
trace_depth = 0;
}
static inline void trace_disable(void)
{
trace = false;
}
static inline void trace_depth_inc(void)
{
if (trace)
trace_depth++;
}
static inline void trace_depth_dec(void)
{
if (trace)
trace_depth--;
}
void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
struct insn_state *snext);
void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
char *alt_name);
void trace_alt_end(struct instruction *orig_insn, struct alternative *alt,
char *alt_name);
#else /* DISAS */
#define TRACE(fmt, ...) ({})
#define TRACE_ADDR(insn, fmt, ...) ({})
#define TRACE_INSN(insn, fmt, ...) ({})
#define TRACE_INSN_STATE(insn, sprev, snext) ({})
#define TRACE_ALT(insn, fmt, ...) ({})
#define TRACE_ALT_INFO(insn, fmt, ...) ({})
#define TRACE_ALT_INFO_NOADDR(insn, fmt, ...) ({})
#define TRACE_ALT_BEGIN(insn, alt, alt_name) ({})
#define TRACE_ALT_END(insn, alt, alt_name) ({})
static inline void trace_enable(void) {}
static inline void trace_disable(void) {}
static inline void trace_depth_inc(void) {}
static inline void trace_depth_dec(void) {}
static inline void trace_alt_begin(struct instruction *orig_insn,
struct alternative *alt,
char *alt_name) {};
static inline void trace_alt_end(struct instruction *orig_insn,
struct alternative *alt,
char *alt_name) {};
#endif
#endif /* _TRACE_H */
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _UTIL_H
#define _UTIL_H
#include <objtool/warn.h>
#define snprintf_check(str, size, format, args...) \
({ \
int __ret = snprintf(str, size, format, args); \
if (__ret < 0) \
ERROR_GLIBC("snprintf"); \
else if (__ret >= size) \
ERROR("snprintf() failed for '" format "'", args); \
else \
__ret = 0; \
__ret; \
})
#endif /* _UTIL_H */
@@ -0,0 +1,168 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _WARN_H
#define _WARN_H
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <objtool/builtin.h>
#include <objtool/elf.h>
extern const char *objname;
static inline char *offstr(struct section *sec, unsigned long offset)
{
bool is_text = (sec->sh.sh_flags & SHF_EXECINSTR);
struct symbol *sym = NULL;
char *str;
int len;
if (is_text)
sym = find_func_containing(sec, offset);
if (!sym)
sym = find_symbol_containing(sec, offset);
if (sym) {
str = malloc(strlen(sym->name) + strlen(sec->name) + 40);
len = sprintf(str, "%s+0x%lx", sym->name, offset - sym->offset);
if (opts.sec_address)
sprintf(str+len, " (%s+0x%lx)", sec->name, offset);
} else {
str = malloc(strlen(sec->name) + 20);
sprintf(str, "%s+0x%lx", sec->name, offset);
}
return str;
}
#define ___WARN(severity, extra, format, ...) \
fprintf(stderr, \
"%s%s%s: objtool" extra ": " format "\n", \
objname ?: "", \
objname ? ": " : "", \
severity, \
##__VA_ARGS__)
#define __WARN(severity, format, ...) \
___WARN(severity, "", format, ##__VA_ARGS__)
#define __WARN_LINE(severity, format, ...) \
___WARN(severity, " [%s:%d]", format, __FILE__, __LINE__, ##__VA_ARGS__)
#define __WARN_ELF(severity, format, ...) \
__WARN_LINE(severity, "%s: " format " failed: %s", __func__, ##__VA_ARGS__, elf_errmsg(-1))
#define __WARN_GLIBC(severity, format, ...) \
__WARN_LINE(severity, "%s: " format " failed: %s", __func__, ##__VA_ARGS__, strerror(errno))
#define __WARN_FUNC(severity, sec, offset, format, ...) \
({ \
char *_str = offstr(sec, offset); \
__WARN(severity, "%s: " format, _str, ##__VA_ARGS__); \
free(_str); \
})
#define WARN_STR (opts.werror ? "error" : "warning")
#define WARN(format, ...) __WARN(WARN_STR, format, ##__VA_ARGS__)
#define WARN_FUNC(sec, offset, format, ...) __WARN_FUNC(WARN_STR, sec, offset, format, ##__VA_ARGS__)
#define WARN_INSN(insn, format, ...) \
({ \
struct instruction *_insn = (insn); \
if (!insn_sym(_insn) || !insn_sym(_insn)->warned) { \
WARN_FUNC(_insn->sec, _insn->offset, format, \
##__VA_ARGS__); \
BT_INSN(_insn, ""); \
} \
if (insn_sym(_insn)) \
insn_sym(_insn)->warned = 1; \
})
#define BT_INSN(insn, format, ...) \
({ \
if (opts.verbose || opts.backtrace) { \
struct instruction *__insn = (insn); \
char *_str = offstr(__insn->sec, __insn->offset); \
const char *_istr = objtool_disas_insn(__insn); \
int _len; \
_len = snprintf(NULL, 0, " %s: " format, _str, ##__VA_ARGS__); \
_len = (_len < 50) ? 50 - _len : 0; \
WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \
free(_str); \
__insn->trace = 1; \
} \
})
#define ERROR_STR "error"
#define ERROR(format, ...) __WARN(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_ELF(format, ...) __WARN_ELF(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_GLIBC(format, ...) __WARN_GLIBC(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_FUNC(sec, offset, format, ...) __WARN_FUNC(ERROR_STR, sec, offset, format, ##__VA_ARGS__)
#define ERROR_INSN(insn, format, ...) ERROR_FUNC(insn->sec, insn->offset, format, ##__VA_ARGS__)
extern bool debug, debug_correlate, debug_clone;
extern int indent;
static inline void unindent(int *unused) { indent--; }
/*
* Clang prior to 17 is being silly and considers many __cleanup() variables
* as unused (because they are, their sole purpose is to go out of scope).
*
* https://github.com/llvm/llvm-project/commit/877210faa447f4cc7db87812f8ed80e398fedd61
*/
#undef __cleanup
#define __cleanup(func) __maybe_unused __attribute__((__cleanup__(func)))
#define __dbg(format, ...) \
fprintf(stderr, \
"DEBUG: %s%s" format "\n", \
objname ?: "", \
objname ? ": " : "", \
##__VA_ARGS__)
#define dbg_checksum_insn(func, insn, checksum) \
({ \
if (unlikely(func->debug_checksum)) { \
char *insn_off = offstr(insn->sec, insn->offset); \
__dbg("checksum: %s(): %s %016llx", \
func->name, insn_off, (unsigned long long)checksum);\
free(insn_off); \
} \
})
#define dbg_checksum_object(sym, offset, what, checksum) \
({ \
if (unlikely(sym->debug_checksum)) \
__dbg("checksum: %s+0x%lx: %s %016llx", \
sym->name, offset, what, \
(unsigned long long)checksum); \
})
#define dbg_correlate(args...) \
({ \
if (unlikely(debug_correlate)) \
__dbg(args); \
})
#define __dbg_clone(format, ...) \
({ \
if (unlikely(debug_clone)) \
__dbg("%*s" format, indent * 8, "", ##__VA_ARGS__); \
})
#define dbg_clone(args...) \
int __cleanup(unindent) __dummy_##__COUNTER__; \
__dbg_clone(args); \
indent++
#endif /* _WARN_H */