My Golang source code is as follows.
package main
func add(x, y int) int {
return x + y
}
func main() {
_ = add(1, 2)
}
The assembly code I obtained using go tool compile -N -l -S main.go > file1.s is as follows(part of it).
;file1.s
"".main STEXT size=54 args=0x0 locals=0x18 funcid=0x0
0x0000 00000 (main.go:7) TEXT "".main(SB), ABIInternal, $24-0
0x0000 00000 (main.go:7) CMPQ SP, 16(R14)
0x0004 00004 (main.go:7) PCDATA $0, $-2
0x0004 00004 (main.go:7) JLS 47
……
0x002f 00047 (main.go:7) CALL runtime.morestack_noctxt(SB)
0x0034 00052 (main.go:7) PCDATA $0, $-1
0x0034 00052 (main.go:7) JMP 0
And the assembly code I obtained using go tool compile -N -l main.go and go tool objdump -S -gnu main.o > file2.s is as follows(part of it).
;file2.s
TEXT "".main(SB) gofile..D:/code/Test/025_go/007_ass/main.go
func main() {
0x5b6 493b6610 CMPQ 0x10(R14), SP // cmp 0x10(%r14),%rsp
0x5ba 7629 JBE 0x5e5 // jbe 0x5e5
……
func main() {
0x5e5 e800000000 CALL 0x5ea // callq 0x5ea [1:5]R_CALL:runtime.morestack_noctxt
0x5ea ebca JMP "".main(SB) // jmp 0x5b6
My questions are:
Why are the source and destination of the CMPQ instructions in file1.s and file2.s opposite, as in CMPQ SP, 16(R14) vs CMPQ 0x10(R14), SP?
For the above two code, my understanding is: when SP <= R14 + 16, call runtime.morestack_noctxt to extend stack. But what I don't understand is: why is SP <= R14 + 16, what is the logic behind? R14 is link register?
Is the code in file2.s a dead loop? Why is it so? Why is the code in file1.s not a dead loop?
What is the meaning of [1:5] in [1:5]R_CALL:runtime.morestack_noctxt in file2.s?
I have a basic knowledge of c++/golang as well as assembly, and I understand the memory layout of programs, but I am really confused about the above questions. Can anyone help me, or what material should I read?
Thank you to everyone who helps me.
Why are the source and destination of the CMPQ instructions in file1.s and file2.s opposite, as in CMPQ SP, 16(R14) vs CMPQ 0x10(R14), SP?
This is likely a bug in the disassembler which I encourage you to file with the Go project. The Go assembler has AT&T operand order for almost all instructions. The CMP family of instructions is a major exception which for easier use has the Intel operand order (i.e. CMPQ foo, bar; JGT baz jumps to baz if foo > bar).
For the above two code, my understanding is: when SP <= R14 + 16, call runtime.morestack_noctxt to extend stack. But what I don't understand is: why is SP <= R14 + 16, what is the logic behind? R14 is link register?
R14 holds a pointer to the g structure corresponding to the currently active Go routine and 0x10(R14) holds the stack limit stackguard0. See user1856856's answer for details. This is a new development following the register ABI proposal. stackguard0 lowest stack address the thread can use before it has to ask the runtime for more stack.
Is the code in file2.s a dead loop? Why is it so? Why is the code in file1.s not a dead loop?
No. When runtime.morestack_noctxt returns, it has changed R14 to the new stack limit, hence the comparison will succeed. It is possible that it will not succeed in which case once again more stack is allocated until it does. This means it is normally not an endless loop.
What is the meaning of [1:5] in [1:5]R_CALL:runtime.morestack_noctxt in file2.s?
This comments hints on the presence of a relocation, indicating that the linker will have to patch in the address of runtime.morestack_noctxt at link time. You can see that the function address in the instruction e800000000 is all zeroes, so as is the call doesn't go anywhere useful. This only changes when the linker resolves the relocation.
for question 2,in amd64 system, R14 is the register that holds the current g,you can check the function
// Append code to p to check for stack split.
// Appends to (does not overwrite) p.
// Assumes g is in rg.
// Returns last new instruction and G register.
func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc, framesize int32, textarg int32) (*obj.Prog, int16) {
// emit...
// Load G register
var rg int16
p, rg = loadG(ctxt, cursym, p, newprog)
var q1 *obj.Prog
if framesize <= objabi.StackSmall {
// small stack: SP <= stackguard
// CMPQ SP, stackguard
p = obj.Appendp(p, newprog)
p.As = cmp
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_SP
p.To.Type = obj.TYPE_MEM
p.To.Reg = rg
p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0
if cursym.CFunc() {
p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1
}
and the loadG
func loadG(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc) (*obj.Prog, int16) {
if ctxt.Arch.Family == sys.AMD64 && cursym.ABI() == obj.ABIInternal {
// Use the G register directly in ABIInternal
return p, REGG
}
var regg int16 = REG_CX
if ctxt.Arch.Family == sys.AMD64 {
regg = REGG // == REG_R14
}
and the file in src/cmd/internal/obj/x86/a.out.go
REGG = REG_R14 // g register in ABIInternal
the g structure is
type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
}
and the stack structure is
// Stack describes a Go execution stack.
// The bounds of the stack are exactly [lo, hi),
// with no implicit data structures on either side.
type stack struct {
lo uintptr
hi uintptr
}
so i think an offset(16) to R14 is the value of current g's stackguard0
Related
Polymorphic function (full code see here):
type Intf interface{
Do()
}
type Intf2 interface {
Intf
Do2()
}
func CallIntf[T Intf](intf T) {
intf.Do()
}
Manual monomorph:
func CallIntf_mono(intf Intf) {
intf.Do()
}
Instantiation:
var intf2 Intf2
intf2 = &impl{}
CallIntf[Intf](intf2)
Instantiation asm (contains a call to runtime.assertI2I()):
LEAQ type."".impl(SB), AX
CALL runtime.newobject(SB)
MOVQ AX, ""..autotmp_13+24(SP)
LEAQ go.itab.*"".impl,"".Intf2(SB), BX
LEAQ type."".Intf(SB), AX
CALL runtime.convI2I(SB)
MOVQ AX, BX
MOVQ ""..autotmp_13+24(SP), CX
LEAQ ""..dict.CallIntf["".Intf](SB), AX
CALL "".CallIntf[go.shape.interface { Do() }_0](SB)
Generated monomorphic function asm (contains a call to runtime.assertI2I()):
TEXT "".CallIntf[go.shape.interface { Do() }_0](SB), DUPOK|ABIInternal, $32-24
MOVQ CX, "".intf+56(SP)
LEAQ type."".Intf(SB), AX
CALL runtime.assertI2I(SB)
MOVQ 24(AX), CX
MOVQ "".intf+56(SP), AX
CALL CX
MOVQ 24(SP), BP
ADDQ $32, SP
RET
Manual monomorph asm (does not call runtime.assertI2I()):
TEXT "".CallIntf_mono(SB), ABIInternal, $16-16
MOVQ AX, "".intf+24(FP)
MOVQ BX, "".intf+32(FP)
MOVQ 24(AX), CX
MOVQ BX, AX
CALL CX
MOVQ 8(SP), BP
ADDQ $16, SP
RET
Question: Why does the assembly of the generated monomorphic function use runtime.assertI2I(), while the one of the manual monomorphic function does not? In which case caller would use a type which needs to be asserted?
I couldn't get confirmation elsewhere, so I'm just going to post my intuition here.
What you see is likely due to the current implementation of Go generics, which is based on GC Stenciling. GC stands for Garbage Collector.
The executive summary is:
[...], for simplicity (and performance) of implementation, we do not have a single compilation of a generic function/method for all possible type arguments. Instead, we share an instantiation of a generic function/method among sets of type arguments that have the same gcshape.
A gcshape is a group of types that "look the same" to the garbage collector. More formally, types that have the same underlying type, or pointers.
The identifier go.shape.interface { Do() }_0 seen in your ASM is the gcshape of the Intf interface, used as a type parameter constraint, where go.shape is a builtin package and interface { Do() } is the underlying type of Intf.
Based on my understanding, the above implies that generic functions aren't truly monomorphized upon instantiation. So the pseudo-register SB ends up having the gcshape of the interface, not the interface itself. For this reason, runtime.assertI2I(SB) is used to ensure that the two interfaces are compatible.
Anyway this is all implementation details. The ASM may change with future Go releases, and even point releases. At the time of writing, the assertion is still present in Go 1.18.3 compiled code.
I want to get the value of EIP from the following code, but the compilation does not pass
Command :
gcc -o xxx x86_inline_asm.c -m32 && ./xxx
file contetn x86_inline_asm.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int eip_val;
__asm__("mov %0,%%eip":"=r"(eip_val));
return 0;
}
How to use the inline assembly to get the value of EIP, and it can be compiled successfully under x86.
How to modify the code and use the command to complete it?
This sounds unlikely to be useful (vs. just taking the address of the whole function like void *tmp = main), but it is possible.
Just get a label address, or use . (the address of the current line), and let the linker worry about getting the right immediate into the machine code. So you're not architecturally reading EIP, just reading the value it currently has from an immediate.
asm volatile("mov $., %0" : "=r"(address_of_mov_instruction) );
AT&T syntax is mov src, dst, so what you wrote would be a jump if it assembled.
(Architecturally, EIP = the end of an instruction while it's executing, so arguably you should do
asm volatile(
"mov $1f, %0 \n\t" // reference label 1 forward
"1:" // GAS local label
"=r"(address_after_mov)
);
I'm using asm volatile in case this asm statement gets duplicated multiple times inside the same function by inlining or something. If you want each case to get a different address, it has to be volatile. Otherwise the compiler can assume that all instances of this asm statement produce the same output. Normally that will be fine.
Architecturally in 32-bit mode you don't have RIP-relative addressing for LEA so the only good way to actually read EIP is call / pop. Reading program counter directly. It's not a general-purpose register so you can't just use it as the source or destination of a mov or any other instruction.
But really you don't need inline asm for this at all.
Is it possible to store the address of a label in a variable and use goto to jump to it? shows how to use the GNU C extension where &&label takes its address.
int foo;
void *addr_inside_function() {
foo++;
lab1: ; // labels only go on statements, not declarations
void *tmp = &&lab1;
foo++;
return tmp;
}
There's nothing you can safely do with this address outside the function; I returned it just as an example to make the compiler put a label in the asm and see what happens. Without a goto to that label, it can still optimize the function pretty aggressively, but you might find it useful as an input for an asm goto(...) somewhere else in the function.
But anyway, it compiles on Godbolt to this asm
# gcc -O3 -m32
addr_inside_function:
.L2:
addl $2, foo
movl $.L2, %eax
ret
#clang -O3 -m32
addr_inside_function:
movl foo, %eax
leal 1(%eax), %ecx
movl %ecx, foo
.Ltmp0: # Block address taken
addl $2, %eax
movl %eax, foo
movl $.Ltmp0, %eax # retval = label address
retl
So clang loads the global, computes foo+1 and stores it, then after the label computes foo+2 and stores that. (Instead of loading twice). So you still can't usefully jump to the label from anywhere, because it depends on having foo's old value in eax, and on the desired behaviour being to store foo+2
I don't know gcc inline assembly syntax for this, but for masm:
call next0
next0: pop eax ;eax = eip for this line
In the case of Masm, $ represents the current location, and since call is a 5 byte instruction, an alternative syntax without a label would be:
call $+5
pop eax
I have been trying to create an ISR handler following this
tutorial by James Molloy but I got stuck. Whenever I throw a software interrupt, general purpose registers and the data segment register is pushed onto the stack with the variables automatically pushed by the CPU. Then the data segment is changed to the value of 0x10 (Kernel Data Segment Descriptor) so the privilege levels are changed. Then after the handler returns those values are poped. But whenever the value in ds is changed a GPE is thrown with the error code 0x2544 and after a few seconds the VM restarts. (linker and compiler i386-elf-gcc , assembler nasm)
I tried placing hlt instructions in between instructions to locate which instruction was throwing the GPE. After that I was able to find out that the the `mov ds,ax' instruction. I tried various things like removing the stack which was initialized by the bootstrap code to deleting the privilege changing parts of the code. The only way I can return from the common stub is to remove the parts of my code which change the privilege levels but as I want to move towards user mode I still want them to stay.
Here is my common stub:
isr_common_stub:
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
xor eax,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; load the kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call isr_handler
xor eax,eax
pop eax
mov ds, ax ; This is the instruction everything fails;
mov es, ax
mov fs, ax
mov gs, ax
popa
iret
My ISR handler macros:
extern isr_handler
%macro ISR_NOERRCODE 1
global isr%1 ; %1 accesses the first parameter.
isr%1:
cli
push byte 0
push %1
jmp isr_common_stub
%endmacro
%macro ISR_ERRCODE 1
global isr%1
isr%1:
cli
push byte %1
jmp isr_common_stub
%endmacro
ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
...
My C handler which results in "Received interrupt: 0xD err. code 0x2544"
#include <stdio.h>
#include <isr.h>
#include <tty.h>
void isr_handler(registers_t regs) {
printf("ds: %x \n" ,regs.ds);
printf("Received interrupt: %x with err. code: %x \n", regs.int_no, regs.err_code);
}
And my main function:
void kmain(struct multiboot *mboot_ptr) {
descinit(); // Sets up IDT and GDT
ttyinit(TTY0); // Sets up the VGA Framebuffer
asm volatile ("int $0x1"); // Triggers a software interrupt
printf("Wow"); // After that its supposed to print this
}
As you can see the code was supposed to output,
ds: 0x10
Received interrupt: 0x1 with err. code: 0
but results in,
...
ds: 0x10
Received interrupt: 0xD with err. code: 0x2544
ds: 0x10
Received interrupt: 0xD with err. code: 0x2544
...
Which goes on until the VM restarts itself.
What am I doing wrong?
The code isn't complete but I'm going to guess what you are seeing is a result of a well known bug in James Molloy's OSDev tutorial. The OSDev community has compiled a list of known bugs in an errata list. I recommend reviewing and fixing all the bugs mentioned there. Specifically in this case I believe the bug that is causing problems is this one:
Problem: Interrupt handlers corrupt interrupted state
This article previously told you to know the ABI. If you do you will
see a huge problem in the interrupt.s suggested by the tutorial: It
breaks the ABI for structure passing! It creates an instance of the
struct registers on the stack and then passes it by value to the
isr_handler function and then assumes the structure is intact
afterwards. However, the function parameters on the stack belongs to
the function and it is allowed to trash these values as it sees fit
(if you need to know whether the compiler actually does this, you are
thinking the wrong way, but it actually does). There are two ways
around this. The most practical method is to pass the structure as a
pointer instead, which allows you to explicitly edit the register
state when needed - very useful for system calls, without having the
compiler randomly doing it for you. The compiler can still edit the
pointer on the stack when it's not specifically needed. The second
option is to make another copy the structure and pass that
The problem is that the 32-bit System V ABI doesn't guarantee that data passed by value will be unmodified on the stack! The compiler is free to reuse that memory for whatever purposes it chooses. The compiler probably generated code that trashed the area on the stack where DS is stored. When DS was set with the bogus value it crashed. What you should be doing is passing by reference rather than value. I'd recommend these code changes in the assembly code:
irq_common_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10 ;0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ; At this point ESP is a pointer to where GS (and the rest
; of the interrupt handler state resides)
; Push ESP as 1st parameter as it's a
; pointer to a registers_t
call irq_handler
pop ebx ; Remove the saved ESP on the stack. Efficient to just pop it
; into any register. You could have done: add esp, 4 as well
pop ebx
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 8
sti
iret
And then modify irq_handler to use registers_t *regs instead of registers_t regs :
void irq_handler(registers_t *regs) {
if (regs->int_no >= 40) port_byte_out(0xA0, 0x20);
port_byte_out(0x20, 0x20);
if (interrupt_handlers[regs->int_no] != 0) {
interrupt_handlers[regs->int_no](*regs);
}
else
{
klog("ISR: Unhandled IRQ%u!\n", regs->int_no);
}
}
I'd actually recommend each interrupt handler take a pointer to registers_t to avoid unnecessary copying. If your interrupt handlers and the interrupt_handlers array used function that took registers_t * as the parameter (instead of registers_t) then you'd modify the code:
interrupt_handlers[r->int_no](*regs);
to be:
interrupt_handlers[r->int_no](regs);
Important: You have to make these same type of changes for your ISR handlers as well. Both the IRQ and ISR handlers and associated code have this same problem.
I'm writing some x86-64 inline assembly that might affect the floating point and media (SSE, MMX, etc.) state, but I don't feel like saving and restoring the state myself. Does Clang/LLVM have a clobber constraint for that?
(I'm not too familiar with the x86-64 architecture or inline assembly, so it was hard to know what to search for. More details in case this is an XY problem: I'm working on a simple coroutine library in Rust. When we switch tasks, we need to store the old CPU state and load the new state, and I'd like to write as little assembly as possible. My guess is that letting the compiler take care of saving and restoring state is the simplest way to do that.)
If your coroutine looks like an opaque (non-inline) function call, the compiler will already assume the FP state is clobbered (except for control regs like MXCSR and the x87 control word (rounding mode)), because all the FP regs are call-clobbered in the normal function calling convention.
Except for Windows, where xmm6..15 are call-preserved.
Also beware that if you're putting a call inside inline asm, there's no way to tell the compiler that your asm clobbers the red zone (128 bytes below RSP in the x86-64 System V ABI). You could compile that file with -mno-redzone or use add rsp, -128 before call to skip over the red-zone that belongs to the compiler-generated code.
To declare clobbers on the FP state, you have to name all the registers separately.
"xmm0", "xmm1", ..., "xmm15" (clobbering xmm0 counts as clobbering ymm0/zmm0).
For good measure you should also name "mm0", ..., "mm7" as well (MMX), in case your code inlines into some legacy code using MMX intrinsics.
To clobber the x87 stack as well, "st" is how you refer to st(0) in the clobber list. The rest of the registers have their normal names for GAS syntax, "st(1)", ..., "st(7)".
https://stackoverflow.com/questions/39728398/how-to-specify-clobbered-bottom-of-the-x87-fpu-stack-with-extended-gcc-assembly
You never know, it is possible to compile withclang -mfpmath=387, or to use 387 vialong double`.
(Hopefully no code uses -mfpmath=387 in 64-bit mode and MMX intrinsics at the same time; the following test-case looks slightly broken with gcc in that case.)
#include <immintrin.h>
float gvar;
int testclobber(float f, char *p)
{
int arg1 = 1, arg2 = 2;
f += gvar; // with -mno-sse, this will be in an x87 register
__m64 mmx_var = *(const __m64*)p; // MMX
mmx_var = _mm_unpacklo_pi8(mmx_var, mmx_var);
// x86-64 System V calling convention
unsigned long long retval;
asm volatile ("add $-128, %%rsp \n\t" // skip red zone. -128 fits in an imm8
"call whatever \n\t"
"sub $-128, %%rsp \n\t"
// FIXME should probably align the stack in here somewhere
: "=a"(retval) // returns in RAX
: "D" (arg1), "S" (arg2) // input args in registers
: "rcx", "rdx", "r8", "r9", "r10", "r11" // call-clobbered integer regs
// call clobbered FP regs, *NOT* including MXCSR
, "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" // MMX
, "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" // x87
// SSE/AVX: clobbering any results in a redundant vzeroupper with gcc?
, "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"
, "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"
#ifdef __AVX512F__
, "zmm16", "zmm17", "zmm18", "zmm19", "zmm20", "zmm21", "zmm22", "zmm23"
, "zmm24", "zmm25", "zmm26", "zmm27", "zmm28", "zmm29", "zmm30", "zmm31"
, "k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"
#endif
#ifdef __MPX__
, "bnd0", "bnd1", "bnd2", "bnd3"
#endif
, "memory" // reads/writes of globals and pointed-to data can't reorder across the asm (at compile time; runtime StoreLoad reordering is still a thing)
);
// Use the MMX var after the asm: compiler has to spill/reload the reg it was in
*(__m64*)p = mmx_var;
_mm_empty(); // emms
gvar = f; // memory clobber prevents hoisting this ahead of the asm.
return retval;
}
source + asm on the Godbolt compiler explorer
By commenting one of the lines of clobbers, we can see that the spill-reload go away in the asm. e.g. commenting the x87 st .. st(7) clobbers makes code that leaves f + gvar in st0, for just a fst dword [gvar] after the call.
Similarly, commenting the mm0 line lets gcc and clang keep mmx_var in mm0 across the call. The ABI requires that the FPU is in x87 mode, not MMX, on call / ret, this isn't really sufficient. The compiler will spill/reload around the asm, but it won't insert an emms for us. But by the same token, it would be an error for a function using MMX to call your co-routine without doing _mm_empty() first, so maybe this isn't a real problem.
I haven't experimented with __m256 variables to see if it inserts a vzeroupper before the asm, to avoid possible SSE/AVX slowdowns.
If we comment the xmm8..15 line, we see the version that isn't using x87 for float keeps it in xmm8, because now it thinks it has some non-clobbered xmm regs. If we comment both sets of lines, it assumes xmm0 lives across the asm, so this works as a test of the clobbers.
asm output with all clobbers in place
It saves/restores RBX (to hold the pointer arg across the asm statement), which happens to re-align the stack by 16. That's another problem with using call from inline asm: I don't think alignment of RSP is guaranteed.
# from clang7.0 -march=skylake-avx512 -mmpx
testclobber: # #testclobber
push rbx
vaddss xmm0, xmm0, dword ptr [rip + gvar]
vmovss dword ptr [rsp - 12], xmm0 # 4-byte Spill (because of xmm0..15 clobber)
mov rbx, rdi # save pointer for after asm
movq mm0, qword ptr [rdi]
punpcklbw mm0, mm0 # mm0 = mm0[0,0,1,1,2,2,3,3]
movq qword ptr [rsp - 8], mm0 # 8-byte Spill (because of mm0..7 clobber)
mov edi, 1
mov esi, 2
add rsp, -128
call whatever
sub rsp, -128
movq mm0, qword ptr [rsp - 8] # 8-byte Reload
movq qword ptr [rbx], mm0
emms # note this didn't happen before call
vmovss xmm0, dword ptr [rsp - 12] # 4-byte Reload
vmovss dword ptr [rip + gvar], xmm0
pop rbx
ret
Notice that because of the "memory" clobber in the asm statement, *p and gvar are read before the asm, but written after. Without that, the optimizer could sink the load or hoist the store so no local variable was live across the asm statement. But now the optimizer needs to assume that the asm statement itself might read the old value of gvar and/or modify it. (And assume that p points to memory that's also globally accessible somehow, because we didn't use __restrict.)
#include <stdio.h>
int main(void){
int sum = 0;
sum += 0xabcd;
printf(“%x”, sum);
return 0;
}
This is my code and when I use gdb I can find different address when break main / break *main.
When I just type disassemble main it shows like this:
Dump of assembler code for function main:
0x080483c4 <+0>: push %ebp
0x080483c5 <+1>: mov %esp,%ebp
0x080483c7 <+3>: and $0xfffffff0,%esp
0x080483ca <+6>: sub $0x20,%esp
0x080483cd <+9>: movl $0x0,0x1c(%esp)
0x080483d5 <+17>:addl $0xabcd,0x1c(%esp)
0x080483dd <+25>:mov $0x80484c0,%eax
0x080483e2 <+30>:mov 0x1c(%esp),%edx
0x080483e6 <+34>:mov %edx,0x4(%esp)
0x080483ea <+38>:mov %eax,(%esp)
0x080483ed <+41>:call 0x80482f4 <printf#plt>
0x080483f2 <+46>:mov $0x0,%eax
0x080483f7 <+51>:leave
0x080483f8 <+52>:ret
End of assembler dump.
So when I type [break *main] it starts 0x080483c4 but type [break main] it start 0x080483cd
Why is start address is different?
Why is the address different.
Because break function and break *address are not the same thing(*address specifies the address of the function's first instruction, before the stack frame and arguments have been set up).
In the first case, GDB skips function prolog (setting up the current frame).
Total guess - and prepared to be totally wrong.
*main if address of the function
Breaking inside main is the first available address to stop inside the function when it is being executed.
Note that 0x080483cd is the first place a debugger can stop as it is modifying a variable (ie assigning zero to sum)
When you are breaking at 0x080483c4 this is before the setup assembler that C knows nothing about