diff options
author | Josh Poimboeuf <jpoimboe@redhat.com> | 2017-06-28 10:11:07 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2017-06-30 10:19:19 +0200 |
commit | baa41469a7b992c1e3db2a39854219cc7442e48f (patch) | |
tree | 1a8e43a0d5e27db457c1cc6f8a4510b258b46bee /tools/objtool | |
parent | c207aee48037abca71c669cbec407b9891965c34 (diff) |
objtool: Implement stack validation 2.0
This is a major rewrite of objtool. Instead of only tracking frame
pointer changes, it now tracks all stack-related operations, including
all register saves/restores.
In addition to making stack validation more robust, this also paves the
way for undwarf generation.
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: live-patching@vger.kernel.org
Link: http://lkml.kernel.org/r/678bd94c0566c6129bcc376cddb259c4c5633004.1498659915.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'tools/objtool')
-rw-r--r-- | tools/objtool/Documentation/stack-validation.txt | 153 | ||||
-rw-r--r-- | tools/objtool/Makefile | 2 | ||||
-rw-r--r-- | tools/objtool/arch.h | 64 | ||||
-rw-r--r-- | tools/objtool/arch/x86/decode.c | 400 | ||||
-rw-r--r-- | tools/objtool/cfi.h | 55 | ||||
-rw-r--r-- | tools/objtool/check.c | 676 | ||||
-rw-r--r-- | tools/objtool/check.h | 19 | ||||
-rw-r--r-- | tools/objtool/elf.c | 59 | ||||
-rw-r--r-- | tools/objtool/elf.h | 6 | ||||
-rw-r--r-- | tools/objtool/special.c | 6 | ||||
-rw-r--r-- | tools/objtool/warn.h | 10 |
11 files changed, 1130 insertions, 320 deletions
diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt index 55a60d331f47..17c1195f11f4 100644 --- a/tools/objtool/Documentation/stack-validation.txt +++ b/tools/objtool/Documentation/stack-validation.txt @@ -127,28 +127,13 @@ b) 100% reliable stack traces for DWARF enabled kernels c) Higher live patching compatibility rate - (NOTE: This is not yet implemented) - - Currently with CONFIG_LIVEPATCH there's a basic live patching - framework which is safe for roughly 85-90% of "security" fixes. But - patches can't have complex features like function dependency or - prototype changes, or data structure changes. - - There's a strong need to support patches which have the more complex - features so that the patch compatibility rate for security fixes can - eventually approach something resembling 100%. To achieve that, a - "consistency model" is needed, which allows tasks to be safely - transitioned from an unpatched state to a patched state. - - One of the key requirements of the currently proposed livepatch - consistency model [*] is that it needs to walk the stack of each - sleeping task to determine if it can be transitioned to the patched - state. If objtool can ensure that stack traces are reliable, this - consistency model can be used and the live patching compatibility - rate can be improved significantly. - - [*] https://lkml.kernel.org/r/cover.1423499826.git.jpoimboe@redhat.com + Livepatch has an optional "consistency model", which is needed for + more complex patches. In order for the consistency model to work, + stack traces need to be reliable (or an unreliable condition needs to + be detectable). Objtool makes that possible. + For more details, see the livepatch documentation in the Linux kernel + source tree at Documentation/livepatch/livepatch.txt. Rules ----- @@ -201,80 +186,84 @@ To achieve the validation, objtool enforces the following rules: return normally. -Errors in .S files ------------------- +Objtool warnings +---------------- -If you're getting an error in a compiled .S file which you don't -understand, first make sure that the affected code follows the above -rules. +For asm files, if you're getting an error which doesn't make sense, +first make sure that the affected code follows the above rules. + +For C files, the common culprits are inline asm statements and calls to +"noreturn" functions. See below for more details. + +Another possible cause for errors in C code is if the Makefile removes +-fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. Here are some examples of common warnings reported by objtool, what they mean, and suggestions for how to fix them. -1. asm_file.o: warning: objtool: func()+0x128: call without frame pointer save/setup +1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup The func() function made a function call without first saving and/or - updating the frame pointer. - - If func() is indeed a callable function, add proper frame pointer - logic using the FRAME_BEGIN and FRAME_END macros. Otherwise, remove - its ELF function annotation by changing ENDPROC to END. + updating the frame pointer, and CONFIG_FRAME_POINTER is enabled. - If you're getting this error in a .c file, see the "Errors in .c - files" section. + If the error is for an asm file, and func() is indeed a callable + function, add proper frame pointer logic using the FRAME_BEGIN and + FRAME_END macros. Otherwise, if it's not a callable function, remove + its ELF function annotation by changing ENDPROC to END, and instead + use the manual CFI hint macros in asm/undwarf.h. + If it's a GCC-compiled .c file, the error may be because the function + uses an inline asm() statement which has a "call" instruction. An + asm() statement with a call instruction must declare the use of the + stack pointer in its output operand. For example, on x86_64: -2. asm_file.o: warning: objtool: .text+0x53: return instruction outside of a callable function - - A return instruction was detected, but objtool couldn't find a way - for a callable function to reach the instruction. + register void *__sp asm("rsp"); + asm volatile("call func" : "+r" (__sp)); - If the return instruction is inside (or reachable from) a callable - function, the function needs to be annotated with the ENTRY/ENDPROC - macros. + Otherwise the stack frame may not get created before the call. - If you _really_ need a return instruction outside of a function, and - are 100% sure that it won't affect stack traces, you can tell - objtool to ignore it. See the "Adding exceptions" section below. +2. file.o: warning: objtool: .text+0x53: unreachable instruction -3. asm_file.o: warning: objtool: func()+0x9: function has unreachable instruction + Objtool couldn't find a code path to reach the instruction. - The instruction lives inside of a callable function, but there's no - possible control flow path from the beginning of the function to the - instruction. + If the error is for an asm file, and the instruction is inside (or + reachable from) a callable function, the function should be annotated + with the ENTRY/ENDPROC macros (ENDPROC is the important one). + Otherwise, the code should probably be annotated with the CFI hint + macros in asm/undwarf.h so objtool and the unwinder can know the + stack state associated with the code. - If the instruction is actually needed, and it's actually in a - callable function, ensure that its function is properly annotated - with ENTRY/ENDPROC. + If you're 100% sure the code won't affect stack traces, or if you're + a just a bad person, you can tell objtool to ignore it. See the + "Adding exceptions" section below. If it's not actually in a callable function (e.g. kernel entry code), change ENDPROC to END. -4. asm_file.o: warning: objtool: func(): can't find starting instruction +4. file.o: warning: objtool: func(): can't find starting instruction or - asm_file.o: warning: objtool: func()+0x11dd: can't decode instruction + file.o: warning: objtool: func()+0x11dd: can't decode instruction - Did you put data in a text section? If so, that can confuse + Does the file have data in a text section? If so, that can confuse objtool's instruction decoder. Move the data to a more appropriate section like .data or .rodata. -5. asm_file.o: warning: objtool: func()+0x6: kernel entry/exit from callable instruction - - This is a kernel entry/exit instruction like sysenter or sysret. - Such instructions aren't allowed in a callable function, and are most - likely part of the kernel entry code. +5. file.o: warning: objtool: func()+0x6: unsupported instruction in callable function - If the instruction isn't actually in a callable function, change - ENDPROC to END. + This is a kernel entry/exit instruction like sysenter or iret. Such + instructions aren't allowed in a callable function, and are most + likely part of the kernel entry code. They should usually not have + the callable function annotation (ENDPROC) and should always be + annotated with the CFI hint macros in asm/undwarf.h. -6. asm_file.o: warning: objtool: func()+0x26: sibling call from callable instruction with changed frame pointer +6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame - This is a dynamic jump or a jump to an undefined symbol. Stacktool + This is a dynamic jump or a jump to an undefined symbol. Objtool assumed it's a sibling call and detected that the frame pointer wasn't first restored to its original state. @@ -282,24 +271,28 @@ they mean, and suggestions for how to fix them. destination code to the local file. If the instruction is not actually in a callable function (e.g. - kernel entry code), change ENDPROC to END. + kernel entry code), change ENDPROC to END and annotate manually with + the CFI hint macros in asm/undwarf.h. -7. asm_file: warning: objtool: func()+0x5c: frame pointer state mismatch +7. file: warning: objtool: func()+0x5c: stack state mismatch The instruction's frame pointer state is inconsistent, depending on which execution path was taken to reach the instruction. - Make sure the function pushes and sets up the frame pointer (for - x86_64, this means rbp) at the beginning of the function and pops it - at the end of the function. Also make sure that no other code in the - function touches the frame pointer. + Make sure that, when CONFIG_FRAME_POINTER is enabled, the function + pushes and sets up the frame pointer (for x86_64, this means rbp) at + the beginning of the function and pops it at the end of the function. + Also make sure that no other code in the function touches the frame + pointer. + Another possibility is that the code has some asm or inline asm which + does some unusual things to the stack or the frame pointer. In such + cases it's probably appropriate to use the CFI hint macros in + asm/undwarf.h. -Errors in .c files ------------------- -1. c_file.o: warning: objtool: funcA() falls through to next function funcB() +8. file.o: warning: objtool: funcA() falls through to next function funcB() This means that funcA() doesn't end with a return instruction or an unconditional jump, and that objtool has determined that the function @@ -318,22 +311,6 @@ Errors in .c files might be corrupt due to a gcc bug. For more details, see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 -2. If you're getting any other objtool error in a compiled .c file, it - may be because the file uses an asm() statement which has a "call" - instruction. An asm() statement with a call instruction must declare - the use of the stack pointer in its output operand. For example, on - x86_64: - - register void *__sp asm("rsp"); - asm volatile("call func" : "+r" (__sp)); - - Otherwise the stack frame may not get created before the call. - -3. Another possible cause for errors in C code is if the Makefile removes - -fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. - -Also see the above section for .S file errors for more information what -the individual error messages mean. If the error doesn't seem to make sense, it could be a bug in objtool. Feel free to ask the objtool maintainer for help. diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 27e019c09bd2..0e2765e243c0 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -25,7 +25,7 @@ OBJTOOL_IN := $(OBJTOOL)-in.o all: $(OBJTOOL) INCLUDES := -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi -CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -fomit-frame-pointer -O2 -g $(INCLUDES) +CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -fomit-frame-pointer -O2 -g $(INCLUDES) LDFLAGS += -lelf $(LIBSUBCMD) # Allow old libelf to be used: diff --git a/tools/objtool/arch.h b/tools/objtool/arch.h index a59e061c0b4a..21aeca874edb 100644 --- a/tools/objtool/arch.h +++ b/tools/objtool/arch.h @@ -19,25 +19,63 @@ #define _ARCH_H #include <stdbool.h> +#include <linux/list.h> #include "elf.h" +#include "cfi.h" -#define INSN_FP_SAVE 1 -#define INSN_FP_SETUP 2 -#define INSN_FP_RESTORE 3 -#define INSN_JUMP_CONDITIONAL 4 -#define INSN_JUMP_UNCONDITIONAL 5 -#define INSN_JUMP_DYNAMIC 6 -#define INSN_CALL 7 -#define INSN_CALL_DYNAMIC 8 -#define INSN_RETURN 9 -#define INSN_CONTEXT_SWITCH 10 -#define INSN_NOP 11 -#define INSN_OTHER 12 +#define INSN_JUMP_CONDITIONAL 1 +#define INSN_JUMP_UNCONDITIONAL 2 +#define INSN_JUMP_DYNAMIC 3 +#define INSN_CALL 4 +#define INSN_CALL_DYNAMIC 5 +#define INSN_RETURN 6 +#define INSN_CONTEXT_SWITCH 7 +#define INSN_STACK 8 +#define INSN_NOP 9 +#define INSN_OTHER 10 #define INSN_LAST INSN_OTHER +enum op_dest_type { + OP_DEST_REG, + OP_DEST_REG_INDIRECT, + OP_DEST_MEM, + OP_DEST_PUSH, + OP_DEST_LEAVE, +}; + +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_ADD, + OP_SRC_AND, +}; + +struct op_src { + enum op_src_type type; + unsigned char reg; + int offset; +}; + +struct stack_op { + struct op_dest dest; + struct op_src src; +}; + +void arch_initial_func_cfi_state(struct cfi_state *state); + int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, unsigned char *type, - unsigned long *displacement); + unsigned long *immediate, struct stack_op *op); + +bool arch_callee_saved_reg(unsigned char reg); #endif /* _ARCH_H */ diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 6ac99e3266eb..a36c2eba64e7 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -27,6 +27,17 @@ #include "../../arch.h" #include "../../warn.h" +static unsigned char op_to_cfi_reg[][2] = { + {CFI_AX, CFI_R8}, + {CFI_CX, CFI_R9}, + {CFI_DX, CFI_R10}, + {CFI_BX, CFI_R11}, + {CFI_SP, CFI_R12}, + {CFI_BP, CFI_R13}, + {CFI_SI, CFI_R14}, + {CFI_DI, CFI_R15}, +}; + static int is_x86_64(struct elf *elf) { switch (elf->ehdr.e_machine) { @@ -40,24 +51,50 @@ static int is_x86_64(struct elf *elf) } } +bool arch_callee_saved_reg(unsigned char reg) +{ + switch (reg) { + case CFI_BP: + case CFI_BX: + case CFI_R12: + case CFI_R13: + case CFI_R14: + case CFI_R15: + return true; + + case CFI_AX: + case CFI_CX: + case CFI_DX: + case CFI_SI: + case CFI_DI: + case CFI_SP: + case CFI_R8: + case CFI_R9: + case CFI_R10: + case CFI_R11: + case CFI_RA: + default: + return false; + } +} + int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, unsigned char *type, - unsigned long *immediate) + unsigned long *immediate, struct stack_op *op) { struct insn insn; - int x86_64; - unsigned char op1, op2, ext; + int x86_64, sign; + unsigned char op1, op2, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, + modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, + sib = 0; x86_64 = is_x86_64(elf); if (x86_64 == -1) return -1; - insn_init(&insn, (void *)(sec->data + offset), maxlen, x86_64); + insn_init(&insn, sec->data->d_buf + offset, maxlen, x86_64); insn_get_length(&insn); - insn_get_opcode(&insn); - insn_get_modrm(&insn); - insn_get_immediate(&insn); if (!insn_complete(&insn)) { WARN_FUNC("can't decode instruction", sec, offset); @@ -73,67 +110,323 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, op1 = insn.opcode.bytes[0]; op2 = insn.opcode.bytes[1]; + if (insn.rex_prefix.nbytes) { + rex = insn.rex_prefix.bytes[0]; + rex_w = X86_REX_W(rex) >> 3; + rex_r = X86_REX_R(rex) >> 2; + rex_b = X86_REX_B(rex); + } + + if (insn.modrm.nbytes) { + modrm = insn.modrm.bytes[0]; + modrm_mod = X86_MODRM_MOD(modrm); + modrm_reg = X86_MODRM_REG(modrm); + modrm_rm = X86_MODRM_RM(modrm); + } + + if (insn.sib.nbytes) + sib = insn.sib.bytes[0]; + switch (op1) { - case 0x55: - if (!insn.rex_prefix.nbytes) - /* push rbp */ - *type = INSN_FP_SAVE; + + case 0x1: + case 0x29: + if (rex_w && !rex_b && modrm_mod == 3 && modrm_rm == 4) { + + /* add/sub reg, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_SRC_REG; + op->dest.reg = CFI_SP; + } + break; + + case 0x50 ... 0x57: + + /* push reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + op->dest.type = OP_DEST_PUSH; + break; - case 0x5d: - if (!insn.rex_prefix.nbytes) - /* pop rbp */ - *type = INSN_FP_RESTORE; + case 0x58 ... 0x5f: + + /* pop reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + + break; + + case 0x68: + case 0x6a: + /* push immediate */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; break; case 0x70 ... 0x7f: *type = INSN_JUMP_CONDITIONAL; break; + case 0x81: + case 0x83: + if (rex != 0x48) + break; + + if (modrm == 0xe4) { + /* and imm, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_AND; + op->src.reg = CFI_SP; + op->src.offset = insn.immediate.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (modrm == 0xc4) + sign = 1; + else if (modrm == 0xec) + sign = -1; + else + break; + + /* add/sub imm, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = insn.immediate.value * sign; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + case 0x89: - if (insn.rex_prefix.nbytes == 1 && - insn.rex_prefix.bytes[0] == 0x48 && - insn.modrm.nbytes && insn.modrm.bytes[0] == 0xe5) - /* mov rsp, rbp */ - *type = INSN_FP_SETUP; + if (rex == 0x48 && modrm == 0xe5) { + + /* mov %rsp, %rbp */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = CFI_SP; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_BP; + break; + } + /* fallthrough */ + case 0x88: + if (!rex_b && + (modrm_mod == 1 || modrm_mod == 2) && modrm_rm == 5) { + + /* mov reg, disp(%rbp) */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_DEST_REG_INDIRECT; + op->dest.reg = CFI_BP; + op->dest.offset = insn.displacement.value; + + } else if (rex_w && !rex_b && modrm_rm == 4 && sib == 0x24) { + + /* mov reg, disp(%rsp) */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_DEST_REG_INDIRECT; + op->dest.reg = CFI_SP; + op->dest.offset = insn.displacement.value; + } + + break; + + case 0x8b: + if (rex_w && !rex_b && modrm_mod == 1 && modrm_rm == 5) { + + /* mov disp(%rbp), reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG_INDIRECT; + op->src.reg = CFI_BP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + + } else if (rex_w && !rex_b && sib == 0x24 && + modrm_mod != 3 && modrm_rm == 4) { + + /* mov disp(%rsp), reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG_INDIRECT; + op->src.reg = CFI_SP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + } + break; case 0x8d: - if (insn.rex_prefix.nbytes && - insn.rex_prefix.bytes[0] == 0x48 && - insn.modrm.nbytes && insn.modrm.bytes[0] == 0x2c && - insn.sib.nbytes && insn.sib.bytes[0] == 0x24) - /* lea %(rsp), %rbp */ - *type = INSN_FP_SETUP; + if (rex == 0x48 && modrm == 0x65) { + + /* lea -disp(%rbp), %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_BP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (rex == 0x4c && modrm == 0x54 && sib == 0x24 && + insn.displacement.value == 8) { + + /* + * lea 0x8(%rsp), %r10 + * + * Here r10 is the "drap" pointer, used as a stack + * pointer helper when the stack gets realigned. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = 8; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_R10; + break; + } + + if (rex == 0x4c && modrm == 0x6c && sib == 0x24 && + insn.displacement.value == 16) { + + /* + * lea 0x10(%rsp), %r13 + * + * Here r13 is the "drap" pointer, used as a stack + * pointer helper when the stack gets realigned. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = 16; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_R13; + break; + } + + if (rex == 0x49 && modrm == 0x62 && + insn.displacement.value == -8) { + + /* + * lea -0x8(%r10), %rsp + * + * Restoring rsp back to its original value after a + * stack realignment. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_R10; + op->src.offset = -8; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (rex == 0x49 && modrm == 0x65 && + insn.displacement.value == -16) { + + /* + * lea -0x10(%r13), %rsp + * + * Restoring rsp back to its original value after a + * stack realignment. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_R13; + op->src.offset = -16; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + break; + + case 0x8f: + /* pop to mem */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; break; case 0x90: *type = INSN_NOP; break; + case 0x9c: + /* pushf */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + break; + + case 0x9d: + /* popf */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; + break; + case 0x0f: + if (op2 >= 0x80 && op2 <= 0x8f) *type = INSN_JUMP_CONDITIONAL; else if (op2 == 0x05 || op2 == 0x07 || op2 == 0x34 || op2 == 0x35) + /* sysenter, sysret */ *type = INSN_CONTEXT_SWITCH; + else if (op2 == 0x0d || op2 == 0x1f) + /* nopl/nopw */ *type = INSN_NOP; - else if (op2 == 0x01 && insn.modrm.nbytes && - (insn.modrm.bytes[0] == 0xc2 || - insn.modrm.bytes[0] == 0xd8)) - /* vmlaunch, vmrun */ - *type = INSN_CONTEXT_SWITCH; + + else if (op2 == 0xa0 || op2 == 0xa8) { + + /* push fs/gs */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + + } else if (op2 == 0xa1 || op2 == 0xa9) { + + /* pop fs/gs */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; + } break; - case 0xc9: /* leave */ - *type = INSN_FP_RESTORE; + case 0xc9: + /* + * leave + * + * equivalent to: + * mov bp, sp + * pop bp + */ + *type = INSN_STACK; + op->dest.type = OP_DEST_LEAVE; + break; - case 0xe3: /* jecxz/jrcxz */ + case 0xe3: + /* jecxz/jrcxz */ *type = INSN_JUMP_CONDITIONAL; break; @@ -158,14 +451,27 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, break; case 0xff: - ext = X86_MODRM_REG(insn.modrm.bytes[0]); - if (ext == 2 || ext == 3) + if (modrm_reg == 2 || modrm_reg == 3) + *type = INSN_CALL_DYNAMIC; - else if (ext == 4) + + else if (modrm_reg == 4) + *type = INSN_JUMP_DYNAMIC; - else if (ext == 5) /*jmpf */ + + else if (modrm_reg == 5) + + /* jmpf */ *type = INSN_CONTEXT_SWITCH; + else if (modrm_reg == 6) { + + /* push from mem */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + } + break; default: @@ -176,3 +482,21 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, return 0; } + +void arch_initial_func_cfi_state(struct cfi_state *state) +{ + int i; + + for (i = 0; i < CFI_NUM_REGS; i++) { + state->regs[i].base = CFI_UNDEFINED; + state->regs[i].offset = 0; + } + + /* initial CFA (call frame address) */ + state->cfa.base = CFI_SP; + state->cfa.offset = 8; + + /* initial RA (return address) */ + state->regs[16].base = CFI_CFA; + state->regs[16].offset = -8; +} diff --git a/tools/objtool/cfi.h b/tools/objtool/cfi.h new file mode 100644 index 000000000000..443ab2c69992 --- /dev/null +++ b/tools/objtool/cfi.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _OBJTOOL_CFI_H +#define _OBJTOOL_CFI_H + +#define CFI_UNDEFINED -1 +#define CFI_CFA -2 +#define CFI_SP_INDIRECT -3 +#define CFI_BP_INDIRECT -4 + +#define CFI_AX 0 +#define CFI_DX 1 +#define CFI_CX 2 +#define CFI_BX 3 +#define CFI_SI 4 +#define CFI_DI 5 +#define CFI_BP 6 +#define CFI_SP 7 +#define CFI_R8 8 +#define CFI_R9 9 +#define CFI_R10 10 +#define CFI_R11 11 +#define CFI_R12 12 +#define CFI_R13 13 +#define CFI_R14 14 +#define CFI_R15 15 +#define CFI_RA 16 +#define CFI_NUM_REGS 17 + +struct cfi_reg { + int base; + int offset; +}; + +struct cfi_state { + struct cfi_reg cfa; + struct cfi_reg regs[CFI_NUM_REGS]; +}; + +#endif /* _OBJTOOL_CFI_H */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 231a36053e07..2f80aa511243 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -27,10 +27,6 @@ #include <linux/hashtable.h> #include <linux/kernel.h> -#define STATE_FP_SAVED 0x1 -#define STATE_FP_SETUP 0x2 -#define STATE_FENTRY 0x4 - struct alternative { struct list_head list; struct instruction *insn; @@ -38,6 +34,7 @@ struct alternative { const char *objname; static bool nofp; +struct cfi_state initial_func_cfi; static struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) @@ -56,7 +53,7 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file, { struct instruction *next = list_next_entry(insn, list); - if (&next->list == &file->insn_list || next->sec != insn->sec) + if (!next || &next->list == &file->insn_list || next->sec != insn->sec) return NULL; return next; @@ -67,7 +64,7 @@ static bool gcov_enabled(struct objtool_file *file) struct section *sec; struct symbol *sym; - list_for_each_entry(sec, &file->elf->sections, list) + for_each_sec(file, sec) list_for_each_entry(sym, &sec->symbol_list, list) if (!strncmp(sym->name, "__gcov_.", 8)) return true; @@ -75,9 +72,6 @@ static bool gcov_enabled(struct objtool_file *file) return false; } -#define for_each_insn(file, insn) \ - list_for_each_entry(insn, &file->insn_list, list) - #define func_for_each_insn(file, func, insn) \ for (insn = find_insn(file, func->sec, func->offset); \ insn && &insn->list != &file->insn_list && \ @@ -94,6 +88,9 @@ static bool gcov_enabled(struct objtool_file *file) #define sec_for_each_insn_from(file, insn) \ for (; insn; insn = next_insn_same_sec(file, insn)) +#define sec_for_each_insn_continue(file, insn) \ + for (insn = next_insn_same_sec(file, insn); insn; \ + insn = next_insn_same_sec(file, insn)) /* * Check if the function has been manually whitelisted with the @@ -103,7 +100,6 @@ static bool gcov_enabled(struct objtool_file *file) static bool ignore_func(struct objtool_file *file, struct symbol *func) { struct rela *rela; - struct instruction *insn; /* check for STACK_FRAME_NON_STANDARD */ if (file->whitelist && file->whitelist->rela) @@ -116,11 +112,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *func) return true; } - /* check if it has a context switching instruction */ - func_for_each_insn(file, func, insn) - if (insn->type == INSN_CONTEXT_SWITCH) - return true; - return false; } @@ -234,6 +225,17 @@ static int dead_end_function(struct objtool_file *file, struct symbol *func) return __dead_end_function(file, func, 0); } +static void clear_insn_state(struct insn_state *state) +{ + int i; + + memset(state, 0, sizeof(*state)); + state->cfa.base = CFI_UNDEFINED; + for (i = 0; i < CFI_NUM_REGS; i++) + state->regs[i].base = CFI_UNDEFINED; + state->drap_reg = CFI_UNDEFINED; +} + /* * Call the arch-specific instruction decoder for all the instructions and add * them to the global instruction list. @@ -246,23 +248,29 @@ static int decode_instructions(struct objtool_file *file) struct instruction *insn; int ret; - list_for_each_entry(sec, &file->elf->sections, list) { + for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; for (offset = 0; offset < sec->len; offset += insn->len) { insn = malloc(sizeof(*insn)); + if (!insn) { + WARN("malloc failed"); + return -1; + } memset(insn, 0, sizeof(*insn)); - INIT_LIST_HEAD(&insn->alts); + clear_insn_state(&insn->state); + insn->sec = sec; insn->offset = offset; ret = arch_decode_instruction(file->elf, sec, offset, sec->len - offset, &insn->len, &insn->type, - &insn->immediate); + &insn->immediate, + &insn->stack_op); if (ret) return ret; @@ -352,7 +360,7 @@ static void add_ignores(struct objtool_file *file) struct section *sec; struct symbol *func; - list_for_each_entry(sec, &file-&g |