Unexpected behaviour in simple pointer arithmetics in kernel space C code [duplicate] - c

I am currently following this workbook on build an operating system.
My intention is to write a 64-bit kernel. I have got as far as loading the "kernel" code and writing individual characters to the frame buffer while in text mode.
My problem appears when I add a level of indirection to writing a single character to the frame buffer by wrapping the code in a function. It would appear that the char value passed into the function is being corrupted in some way.
I have three files:
bootloader.asm
; bootloader.asm
[org 0x7c00]
KERNEL_OFFSET equ 0x1000
mov bp, 0x9000
mov sp, bp
; load the kernel from boot disk
mov bx, KERNEL_OFFSET
mov dl, dl ; boot drive is set to dl
mov ah, 0x02 ; bios read sector
mov al, 15 ; read 15 sectors
mov ch, 0x00 ; cylinder 0
mov cl, 0x02 ; read from 2nd sector
mov dh, 0x00 ; select head 0
int 0x13
; THERE COULD BE ERRORS HERE BUT FOR NOW ASSUME IT WORKS
; switch to protected mode
cli
lgdt [gdt.descriptor]
mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_SEGMENT:start_protected_mode
[bits 32]
start_protected_mode:
mov ax, DATA_SEGMENT
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000
mov esp, ebp
call KERNEL_OFFSET
jmp $
[bits 16]
gdt: ; Super Simple Global Descriptor Table
.start:
.null:
dd 0x0
dd 0x0
.code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
.data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
.end:
.descriptor:
dw .end - .start
dd .start
CODE_SEGMENT equ gdt.code - gdt.start
DATA_SEGMENT equ gdt.data - gdt.start
times 510-($-$$) db 0
dw 0xaa55
bootkernel.asm
[bits 32]
[extern main]
[global _start]
_start:
call main
jmp $
kernel.c
// LEGACY MODE VIDEO DRIVER
#define FRAME_BUFFER_ADDRESS 0xb8002
#define GREY_ON_BLACK 0x07
#define WHITE_ON_BLACK 0x0f
void write_memory(unsigned long address, unsigned int index, unsigned char value)
{
unsigned char * memory = (unsigned char *) address;
memory[index] = value;
}
unsigned int frame_buffer_offset(unsigned int col, unsigned int row)
{
return 2 * ((row * 80u) + col);
}
void write_frame_buffer_cell(unsigned char c, unsigned char a, unsigned int col, unsigned int row)
{
unsigned int offset = frame_buffer_offset(col, row);
write_memory(FRAME_BUFFER_ADDRESS, offset, c);
write_memory(FRAME_BUFFER_ADDRESS, offset + 1, a);
}
void main()
{
unsigned int offset = frame_buffer_offset(0, 1);
write_memory(FRAME_BUFFER_ADDRESS, offset, 'A');
write_memory(FRAME_BUFFER_ADDRESS, offset + 1, GREY_ON_BLACK);
write_frame_buffer_cell('B', GREY_ON_BLACK, 0, 1);
}
The .text section is linked to start from 0x1000 which is where the bootloader expects the kernel to start.
The linker.ld script is
SECTIONS
{
. = 0x1000;
.text : { *(.text) } /* Kernel is expected at 0x1000 */
}
The Make file that puts this all together is:
bootloader.bin: bootloader.asm
nasm -f bin bootloader.asm -o bootloader.bin
bootkernel.o: bootkernel.asm
nasm -f elf64 bootkernel.asm -o bootkernel.o
kernel.o: kernel.c
gcc-6 -Wextra -Wall -ffreestanding -c kernel.c -o kernel.o
kernel.bin: bootkernel.o kernel.o linker.ld
ld -o kernel.bin -T linker.ld bootkernel.o kernel.o --oformat binary
os-image: bootloader.bin kernel.bin
cat bootloader.bin kernel.bin > os-image
qemu: os-image
qemu-system-x86_64 -d guest_errors -fda os-image -boot a
I've taken a screen shot of the output that I am getting. I expect 'A' to appear in the 0th column of the 1st row and for 'B' to appear on the 1st column of the 0th row. For some reason I am getting another character.
Output of gcc-6 -S kernel.c
.file "kernel.c"
.text
.globl write_memory
.type write_memory, #function
write_memory:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -24(%rbp)
movl %esi, -28(%rbp)
movl %edx, %eax
movb %al, -32(%rbp)
movq -24(%rbp), %rax
movq %rax, -8(%rbp)
movl -28(%rbp), %edx
movq -8(%rbp), %rax
addq %rax, %rdx
movzbl -32(%rbp), %eax
movb %al, (%rdx)
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size write_memory, .-write_memory
.globl frame_buffer_offset
.type frame_buffer_offset, #function
frame_buffer_offset:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -8(%rbp), %edx
movl %edx, %eax
sall $2, %eax
addl %edx, %eax
sall $4, %eax
movl %eax, %edx
movl -4(%rbp), %eax
addl %edx, %eax
addl %eax, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size frame_buffer_offset, .-frame_buffer_offset
.globl write_frame_buffer_cell
.type write_frame_buffer_cell, #function
write_frame_buffer_cell:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %esi, %eax
movl %edx, -28(%rbp)
movl %ecx, -32(%rbp)
movb %dil, -20(%rbp)
movb %al, -24(%rbp)
movl -32(%rbp), %edx
movl -28(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call frame_buffer_offset
movl %eax, -4(%rbp)
movzbl -20(%rbp), %edx
movl -4(%rbp), %eax
movl %eax, %esi
movl $753666, %edi
call write_memory
movzbl -24(%rbp), %eax
movl -4(%rbp), %edx
leal 1(%rdx), %ecx
movl %eax, %edx
movl %ecx, %esi
movl $753666, %edi
call write_memory
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size write_frame_buffer_cell, .-write_frame_buffer_cell
.globl main
.type main, #function
main:
.LFB3:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, %esi
movl $0, %edi
call frame_buffer_offset
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
movl $65, %edx
movl %eax, %esi
movl $753666, %edi
call write_memory
movl -4(%rbp), %eax
addl $1, %eax
movl $7, %edx
movl %eax, %esi
movl $753666, %edi
call write_memory
movl $0, %ecx
movl $1, %edx
movl $7, %esi
movl $66, %edi
call write_frame_buffer_cell
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE3:
.size main, .-main
.ident "GCC: (Ubuntu 6.2.0-3ubuntu11~16.04) 6.2.0 20160901"
.section .note.GNU-stack,"",#progbits

I can reproduce your exact output if the code is modified to be:
unsigned int offset = frame_buffer_offset(0, 1);
write_memory(FRAME_BUFFER_ADDRESS, offset, 'A');
write_memory(FRAME_BUFFER_ADDRESS, offset + 1, GREY_ON_BLACK);
write_frame_buffer_cell('B', GREY_ON_BLACK, 1, 0);
The difference being in the last line ('B', GREY_ON_BLACK, 1, 0);. Originally you had ('B', GREY_ON_BLACK, 0, 1); . This is in line with what you described you were trying to do when you said:
I've taken a screen shot of the output that I am getting. I expect 'A' to appear in the 0th column of the 1st row and for 'B' to appear on the 1st column of the 0th row.
I gather you may have posted the wrong code in this question. This is the output I get:
It seems you are new to OS development. Your bootloader code only places the CPU into 32-bit protected mode, but to run a 64-bit kernel you need to be in 64-bit longmode. If you are just getting started I'd suggest falling back to writing a 32-bit kernel for purposes of learning at this early stage. At the bottom I have a 64-bit long mode section with a link to a longmode tutorial that could be used to modify your bootloader to run 64-bit code.
Primary Issue Causing Unusual Behaviour
You are experiencing an issue primarily related to the fact that you are generating 64-bit code with GCC but you are running it in 32-bit protected mode according to your bootloader code. 64-bit code generation running in 32-bit protected mode may appear to execute, but it will do it incorrectly. In simple OSes where you are simply displaying to the video display you may often see unexpected output as a side effect. Your program could triple fault the machine, but you got unlucky that the side effect seemed to display something on the video display. You may have been under the false impression that things were working as they should when they really weren't.
This question is somewhat similar to another Stackoverflow question. After the original poster of that question made available a complete example it became clear that it was his issue. Part of my answer to him to resolve the issue was as follows:
Likely Cause of Undefined Behavior
After all the code and the make file were made available in EDIT 2 it became clear that one significant problem was that most of the code was compiled and linked to 64-bit objects and executables. That code won't work in 32-bit protected mode.
In the make file make these adjustments:
When compiling with GCC you need to add -m32 option
When assembling with GNU Assembler (as) targeting 32-bit objects you need to use --32
When linking with LD you need to add the -melf_i386 option
When assembling with NASM targeting 32-bit objects you need to change -f elf64 to -f elf32
With that in mind you can alter your Makefile to generate 32-bit code. It could look like:
bootloader.bin: bootloader.asm
nasm -f bin bootloader.asm -o bootloader.bin
bootkernel.o: bootkernel.asm
nasm -f elf32 bootkernel.asm -o bootkernel.o
kernel.o: kernel.c
gcc-6 -m32 -Wextra -Wall -ffreestanding -c kernel.c -o kernel.o
kernel.bin: bootkernel.o kernel.o linker.ld
ld -melf_i386 -o kernel.bin -T linker.ld bootkernel.o kernel.o --oformat binary
os-image: bootloader.bin kernel.bin
cat bootloader.bin kernel.bin > os-image
qemu: os-image
qemu-system-x86_64 -d guest_errors -fda os-image -boot a
I gather when you started having issues with your code you ended up trying 0xb8002 as the address for your video memory. It should be 0xb8000. You'll need to modify:
#define FRAME_BUFFER_ADDRESS 0xb8002
To be:
#define FRAME_BUFFER_ADDRESS 0xb8000
Making all these changes should resolve your issues. This is what the output I got looked like after the changes mentioned above:
Other observations
In write_memory you use:
unsigned char * memory = (unsigned char *) address;
Since you are using 0xb8000 that is memory mapped to the video display you should mark it as volatile since a compiler could optimize things away not knowing that there is a side effect to writing to that memory (namely displaying characters on a display). You might wish to use:
volatile unsigned char * memory = (unsigned char *) address;
In your bootloader.asm You really should explicitly set the A20 line on. You can find information about doing that in this OSDev Wiki article. The status of the A20 line at the point a bootloader starts executing may vary between emulators. Failure to set it on could cause issues if you try to access memory areas on an odd numbered megabyte boundary (like 0x100000 to 0x1fffff, 0x300000 to 0x1fffff etc). Accesses to the odd numbered megabyte memory regions will actually read data from the even numbered memory region just below it. This is usually not behaviour you want.
64-bit long mode
If you want to run 64-bit code you will need to place the processor into 64-bit long mode. This is a bit more involved than entering 32-bit protected mode. Information on 64-bit longmode can be found in the OSDev wiki. Once properly in 64-bit longmode you can use 64-bit instructions generated by GCC.

Related

Why gdb shows different addresses in RIP-relative mode from absolute address?

Having this in c:
#include <stdio.h>
#include <stdlib.h>
int x;
int main(){
printf("eneter x\n");
scanf("%i",&x);
printf("you enetered: %i\n", x);
return 0;
}
in gdb:
starti
disas main
0x0000555555555155 <+0>: push %rbp
0x0000555555555156 <+1>: mov %rsp,%rbp
0x0000555555555159 <+4>: lea 0xea4(%rip),%rdi # 0x555555556004
0x0000555555555160 <+11>: callq 0x555555555030 <puts#plt>
0x0000555555555165 <+16>: lea 0x2ed8(%rip),%rsi # 0x555555558044 <x>
0x000055555555516c <+23>: lea 0xe9a(%rip),%rdi # 0x55555555600d
0x0000555555555173 <+30>: mov $0x0,%eax
0x0000555555555178 <+35>: callq 0x555555555050 <__isoc99_scanf#plt>
0x000055555555517d <+40>: mov 0x2ec1(%rip),%eax # 0x555555558044 <x>
0x0000555555555183 <+46>: mov %eax,%esi
0x0000555555555185 <+48>: lea 0xe84(%rip),%rdi # 0x555555556010
0x000055555555518c <+55>: mov $0x0,%eax
0x0000555555555191 <+60>: callq 0x555555555040 <printf#plt>
0x0000555555555196 <+65>: mov $0x0,%eax
0x000055555555519b <+70>: pop %rbp
0x000055555555519c <+71>: retq
here the relative address of x variable is $rip+0x2ed8 (from instruction lea 0x2ed8(%rip),%rsi # 0x555555558044). But as you can see in the comment #, the absolute address is 0x555555558044. Ok will I get that address when try to read from the relative one? Lets see:
x $rip+0x2ed8
0x555555558055: 0x00000000
nop - relative address did not use the absolute address, where the x var is really stored (0x555555558055 != 0x555555558044) the difference is 17 bytes. Is it the number of bytes of the instruction itself (lea + operands)? I do not know, but do not think so. So why does relative and absolute addressing differ in gdb?
PS, generated assembly:
.file "a.c"
.comm x,4,4
.section .rodata
.LC0:
.string "eneter x"
.LC1:
.string "%i"
.LC2:
.string "you enetered: %i\n"
.text
.globl main
.type main, #function
main:
pushq %rbp #
movq %rsp, %rbp #,
# a.c:5: printf("eneter x\n");
leaq .LC0(%rip), %rdi #,
call puts#PLT #
# a.c:6: scanf("%i",&x);
leaq x(%rip), %rsi #,
leaq .LC1(%rip), %rdi #,
movl $0, %eax #,
call __isoc99_scanf#PLT #
# a.c:7: printf("you enetered: %i\n", x);
movl x(%rip), %eax # x, x.0_1
movl %eax, %esi # x.0_1,
leaq .LC2(%rip), %rdi #,
movl $0, %eax #,
call printf#PLT #
# a.c:8: return 0;
movl $0, %eax #, _6
# a.c:9: }
popq %rbp #
ret
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",#progbits
Here, the RIP-relative mode is used:
# a.c:6: scanf("%i",&x);
leaq x(%rip), %rsi #,
where the x is position of the x symbol. But in comments, someone said, that $rip+0x2ed8 is not the same, and the offset 0x2ed8 does not lead to the address of the x. But why those two differ? but should be RIP-relative mode addressing and both should gain the same offset (and thus address).
0x0000555555555165 <+16>: lea 0x2ed8(%rip),%rsi # 0x555555558044 <x>
0x000055555555516c <+23>: lea 0xe9a(%rip),%rdi # 0x55555555600d
A RIP relative address in an instruction is relative to the address just after the current instruction (i.e. the address of the instruction plus the size of the instruction, or the address of the following instruction). This is because when the instruction has been loaded into the processor, the RIP register is advanced by the size of the current instruction just before it is executed. (At least that is the model that is followed even though modern processors use all sorts of tricks behind the scenes to speed up execution.) (Note: The above is true for several CPU architectures, including x86 variants, but some other CPU architectures differ in the point from which PC-relative addresses are measured1.)
The first instruction above is at address 0x555555555165 and the following instruction is at address 0x55555555516c (the instruction is 7 bytes long). In the first instruction, the RIP relative address 0x2ed8(%rip) refers to 0x2ed8 + 0x000055555555516c = 0x555555558044.
Note that if you set a breakpoint on an instruction in a debugger and show the registers when the breakpoint is reached, RIP will point to the current instruction, not the next one, because the current instruction is not being executed yet.
1 Thanks to Peter Cordes for details about PC-relative addressing for ARM and RISC-V CPU architectures.

Can someone please tell me whats wrong with the following Assembl code. Getting Segmentation fault

Getting segmentation fault in the function, due to instruction
movq -8(%rbp), %rax, one before the printf. I can't understand why ?
Note : this is not gcc generated assembly, but by compiler i am writing. Assembly code is almost similar to what gcc generates.
.text
.globl main
.type main, #function
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $2, -4(%rbp)
leaq -4(%rbp), %rax
movl %eax, %edi
movb $0, %al
call fcvt2
movl %eax, -4(%rbp)
leaq .LC0(%rip), %rdi
movl -4(%rbp), %esi
movb $0, %al
call printf
leave
ret
.globl fcvt2
.type fcvt2, #function
fcvt2:
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rdi, -8(%rbp)
leaq .LC1(%rip), %rdi
movq -8(%rbp), %rax
movl (%rax), %esi
movb $0, %al
call printf
movq -8(%rbp), %rax
movl (%rax), %edi
movl %edi, %eax
leave
ret
.section .rodata
.LC1:
.string "It should be : %d\f"
.LC0:
.string "%d\n"
And C Program is :
int fcvt2(int *ip) {
int i;
printf("It should be : %d\f", *ip);
return *ip;
}
void main() {
int i;
i = 2;
i = fcvt2(&i);
printf("%d\n",i);
return;
}
gdb output at fault point:
rax 0xffffdd4c 4294958412
rbx 0x0 0
rcx 0x7ffffff7 2147483639
rdx 0x7ffff7dd3780 140737351858048
rsi 0x7fffffffdd48 140737488346440
rdi 0xffffdd4c 4294958412
rbp 0x7fffffffdd30 0x7fffffffdd30
rsp 0x7fffffffdd00 0x7fffffffdd00
r8 0x0 0
r9 0x9 9
r10 0x7ffff7dd1b78 140737351850872
r11 0x246 582
r12 0x400430 4195376
r13 0x7fffffffde30 140737488346672
r14 0x0 0
r15 0x0 0
rip 0x40059c 0x40059c <fcvt2+20>
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
movl %eax, %edi in the caller truncates the pointer arg to fcvt2. You actually segfault on mov (%rax),%esi. rax, not the instruction before it like you claimed. (Time for a refresher on your GDB skills?)
leaq -4(%rbp), %rax generated it correctly in %rax, but then your compiler forgot that it was a 64-bit pointer to a 32-bit value. (Ideally you'd want to leaq -4(%rbp), %rdi directly into the arg register.)
Off topic: if you don't need to preserve the upper bytes of EAX, movb $0, %al is less efficient than xor %eax, %eax. I think you're doing this for the x86-64 SysV variadic function convention, and you're right that only %al needs to say how many XMM register args there are, not the whole %eax, so you got that right. But zeroing eax is the most efficient way to zero al. Of course, you don't need to do this at all for non-variadic functions, but your compiler is obviously still in the just-get-it-working phase, so doing it unconditionally isn't a correctness problem; you never need to pass anything else in rax, and function calls are always assumed to clobber rax.
(Also related: Haswell/Skylake partial registers have false dependencies: al isn't renamed separately from rax anymore)

Wrong return value when calling NASM on x86-64 from C

I'm studying NASM on Linux 64-bit and have been trying to implement some examples of code. However I got a problem in the following example. The function donothing is implemented in NASM and is supposed to be called in a program implemented in C:
File main.c:
#include <stdio.h>
#include <stdlib.h>
int donothing(int, int);
int main() {
printf(" == %d\n", donothing(1, 2));
return 0;
}
File first.asm
global donothing
section .text
donothing:
push rbp
mov rbp, rsp
mov eax, [rbp-0x4]
pop rbp
ret
What donothing does is nothing more than returning the value of the first parameter. But when donothing is called the value 0 is printed instead of 1. I tried rbp+0x4, but it doesn't work too.
I compile the files using the following command:
nasm -f elf64 first.asm && gcc first.o main.c
Compiling the function 'test' in C by using gcc -s the assembly code generated to get the parameters looks similar to the donothing:
int test(int a, int b) {
return a > b;
}
Assembly generated by gcc for the function 'test' above:
test:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
cmpl -8(%rbp), %eax
setg %al
movzbl %al, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
So, what's wrong with donothing?
In x86-64 calling conventions the first few parameters are passed in registers rather than on the stack. In your case you should find the 1 and 2 in RDI and RSI.
As you can see in the compiled C code, it takes a from edi and b from esi (although it goes through an unnecessary intermediate step by placing them in memory)

Creating a directory in linux assembly language

I am trying to create a small assembly program to create a folder. I looked up the system call for creating a directory on this page. It says that it is identified by 27h. How would I go about implementing the mkdir somename in assembly?
I am aware that the program should move 27 into eax but I am unsure where to go next. I have googled quite a bit and no one seems to have posted anthing about this online.
This is my current code (I don't know in which register to put filename and so on):
section .data
section .text
global _start
mov eax, 27
mov ????????
....
int 80h
Thanks
One way of finding out, is using GCC to translate the following C code:
#include <stdio.h>
#include <sys/stat.h>
int main()
{
if (mkdir("testdir", 0777) != 0)
{
return -1;
}
return 0;
}
to assembly, with: gcc mkdir.c -S
.file "mkdir.c"
.section .rodata
.LC0:
.string "testdir"
.text
.globl main
.type main, #function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $511, 4(%esp)
movl $.LC0, (%esp)
call mkdir ; interesting call
testl %eax, %eax
setne %al
testb %al, %al
je .L2
movl $-1, %eax
jmp .L3
.L2:
movl $0, %eax
.L3:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.5.1 20100924 (Red Hat 4.5.1-4)"
.section .note.GNU-stack,"",#progbits
Anyway, ProgrammingGroundUp page 272 lists important syscalls, including mkdir:
%eax Name %ebx %ecx %edx Notes
------------------------------------------------------------------
39 mkdir NULL terminated Permission Creates the given
directory name directory. Assumes all
directories leading up
to it already exist.
You could also do like the Assembly Howto is suggesting. But indeed, calling mkdir from Libc is more portable. You need to look into asm/unistd.h to get the syscall number.

Understand the assembly code generated by a simple C program

I am trying to understand the assembly level code for a simple C program by inspecting it with gdb's disassembler.
Following is the C code:
#include <stdio.h>
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
Following is the disassembly code for both main and function
gdb) disass main
Dump of assembler code for function main:
0x08048428 <main+0>: push %ebp
0x08048429 <main+1>: mov %esp,%ebp
0x0804842b <main+3>: and $0xfffffff0,%esp
0x0804842e <main+6>: sub $0x10,%esp
0x08048431 <main+9>: movl $0x3,0x8(%esp)
0x08048439 <main+17>: movl $0x2,0x4(%esp)
0x08048441 <main+25>: movl $0x1,(%esp)
0x08048448 <main+32>: call 0x8048404 <function>
0x0804844d <main+37>: leave
0x0804844e <main+38>: ret
End of assembler dump.
(gdb) disass function
Dump of assembler code for function function:
0x08048404 <function+0>: push %ebp
0x08048405 <function+1>: mov %esp,%ebp
0x08048407 <function+3>: sub $0x28,%esp
0x0804840a <function+6>: mov %gs:0x14,%eax
0x08048410 <function+12>: mov %eax,-0xc(%ebp)
0x08048413 <function+15>: xor %eax,%eax
0x08048415 <function+17>: mov -0xc(%ebp),%eax
0x08048418 <function+20>: xor %gs:0x14,%eax
0x0804841f <function+27>: je 0x8048426 <function+34>
0x08048421 <function+29>: call 0x8048340 <__stack_chk_fail#plt>
0x08048426 <function+34>: leave
0x08048427 <function+35>: ret
End of assembler dump.
I am seeking answers for following things :
how the addressing is working , I mean (main+0) , (main+1), (main+3)
In the main, why is $0xfffffff0,%esp being used
In the function, why is %gs:0x14,%eax , %eax,-0xc(%ebp) being used.
If someone can explain , step by step happening, that will be greatly appreciated.
The reason for the "strange" addresses such as main+0, main+1, main+3, main+6 and so on, is because each instruction takes up a variable number of bytes. For example:
main+0: push %ebp
is a one-byte instruction so the next instruction is at main+1. On the other hand,
main+3: and $0xfffffff0,%esp
is a three-byte instruction so the next instruction after that is at main+6.
And, since you ask in the comments why movl seems to take a variable number of bytes, the explanation for that is as follows.
Instruction length depends not only on the opcode (such as movl) but also the addressing modes for the operands as well (the things the opcode are operating on). I haven't checked specifically for your code but I suspect the
movl $0x1,(%esp)
instruction is probably shorter because there's no offset involved - it just uses esp as the address. Whereas something like:
movl $0x2,0x4(%esp)
requires everything that movl $0x1,(%esp) does, plus an extra byte for the offset 0x4.
In fact, here's a debug session showing what I mean:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
c:\pax> debug
-a
0B52:0100 mov word ptr [di],7
0B52:0104 mov word ptr [di+2],8
0B52:0109 mov word ptr [di+0],7
0B52:010E
-u100,10d
0B52:0100 C7050700 MOV WORD PTR [DI],0007
0B52:0104 C745020800 MOV WORD PTR [DI+02],0008
0B52:0109 C745000700 MOV WORD PTR [DI+00],0007
-q
c:\pax> _
You can see that the second instruction with an offset is actually different to the first one without it. It's one byte longer (5 bytes instead of 4, to hold the offset) and actually has a different encoding c745 instead of c705.
You can also see that you can encode the first and third instruction in two different ways but they basically do the same thing.
The and $0xfffffff0,%esp instruction is a way to force esp to be on a specific boundary. This is used to ensure proper alignment of variables. Many memory accesses on modern processors will be more efficient if they follow the alignment rules (such as a 4-byte value having to be aligned to a 4-byte boundary). Some modern processors will even raise a fault if you don't follow these rules.
After this instruction, you're guaranteed that esp is both less than or equal to its previous value and aligned to a 16 byte boundary.
The gs: prefix simply means to use the gs segment register to access memory rather than the default.
The instruction mov %eax,-0xc(%ebp) means to take the contents of the ebp register, subtract 12 (0xc) and then put the value of eax into that memory location.
Re the explanation of the code. Your function function is basically one big no-op. The assembly generated is limited to stack frame setup and teardown, along with some stack frame corruption checking which uses the afore-mentioned %gs:14 memory location.
It loads the value from that location (probably something like 0xdeadbeef) into the stack frame, does its job, then checks the stack to ensure it hasn't been corrupted.
Its job, in this case, is nothing. So all you see is the function administration stuff.
Stack set-up occurs between function+0 and function+12. Everything after that is setting up the return code in eax and tearing down the stack frame, including the corruption check.
Similarly, main consist of stack frame set-up, pushing the parameters for function, calling function, tearing down the stack frame and exiting.
Comments have been inserted into the code below:
0x08048428 <main+0>: push %ebp ; save previous value.
0x08048429 <main+1>: mov %esp,%ebp ; create new stack frame.
0x0804842b <main+3>: and $0xfffffff0,%esp ; align to boundary.
0x0804842e <main+6>: sub $0x10,%esp ; make space on stack.
0x08048431 <main+9>: movl $0x3,0x8(%esp) ; push values for function.
0x08048439 <main+17>: movl $0x2,0x4(%esp)
0x08048441 <main+25>: movl $0x1,(%esp)
0x08048448 <main+32>: call 0x8048404 <function> ; and call it.
0x0804844d <main+37>: leave ; tear down frame.
0x0804844e <main+38>: ret ; and exit.
0x08048404 <func+0>: push %ebp ; save previous value.
0x08048405 <func+1>: mov %esp,%ebp ; create new stack frame.
0x08048407 <func+3>: sub $0x28,%esp ; make space on stack.
0x0804840a <func+6>: mov %gs:0x14,%eax ; get sentinel value.
0x08048410 <func+12>: mov %eax,-0xc(%ebp) ; put on stack.
0x08048413 <func+15>: xor %eax,%eax ; set return code 0.
0x08048415 <func+17>: mov -0xc(%ebp),%eax ; get sentinel from stack.
0x08048418 <func+20>: xor %gs:0x14,%eax ; compare with actual.
0x0804841f <func+27>: je <func+34> ; jump if okay.
0x08048421 <func+29>: call <_stk_chk_fl> ; otherwise corrupted stack.
0x08048426 <func+34>: leave ; tear down frame.
0x08048427 <func+35>: ret ; and exit.
I think the reason for the %gs:0x14 may be evident from above but, just in case, I'll elaborate here.
It uses this value (a sentinel) to put in the current stack frame so that, should something in the function do something silly like write 1024 bytes to a 20-byte array created on the stack or, in your case:
char buffer1[5];
strcpy (buffer1, "Hello there, my name is Pax.");
then the sentinel will be overwritten and the check at the end of the function will detect that, calling the failure function to let you know, and then probably aborting so as to avoid any other problems.
If it placed 0xdeadbeef onto the stack and this was changed to something else, then an xor with 0xdeadbeef would produce a non-zero value which is detected in the code with the je instruction.
The relevant bit is paraphrased here:
mov %gs:0x14,%eax ; get sentinel value.
mov %eax,-0xc(%ebp) ; put on stack.
;; Weave your function
;; magic here.
mov -0xc(%ebp),%eax ; get sentinel back from stack.
xor %gs:0x14,%eax ; compare with original value.
je stack_ok ; zero/equal means no corruption.
call stack_bad ; otherwise corrupted stack.
stack_ok: leave ; tear down frame.
Pax has produced a definitive answer. However, for completeness, I thought I'd add a note on getting GCC itself to show you the assembly it generates.
The -S option to GCC tells it to stop compilation and write the assembly to a file. Normally, it either passes that file to the assembler or for some targets writes the object file directly itself.
For the sample code in the question:
#include <stdio.h>
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
the command gcc -S q3654898.c creates a file named q3654898.s:
.file "q3654898.c"
.text
.globl _function
.def _function; .scl 2; .type 32; .endef
_function:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
leave
ret
.def ___main; .scl 2; .type 32; .endef
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
call __alloca
call ___main
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)
call _function
leave
ret
One thing that is evident is that my GCC (gcc (GCC) 3.4.5 (mingw-vista special r3)) doesn't include the stack check code by default. I imagine that there is a command line option, or that if I ever got around to nudging my MinGW install up to a more current GCC that it could.
Edit: Nudged to do so by Pax, here's another way to get GCC to do more of the work.
C:\Documents and Settings\Ross\My Documents\testing>gcc -Wa,-al q3654898.c
q3654898.c: In function `main':
q3654898.c:8: warning: return type of 'main' is not `int'
GAS LISTING C:\DOCUME~1\Ross\LOCALS~1\Temp/ccLg8pWC.s page 1
1 .file "q3654898.c"
2 .text
3 .globl _function
4 .def _function; .scl 2; .type
32; .endef
5 _function:
6 0000 55 pushl %ebp
7 0001 89E5 movl %esp, %ebp
8 0003 83EC28 subl $40, %esp
9 0006 C9 leave
10 0007 C3 ret
11 .def ___main; .scl 2; .type
32; .endef
12 .globl _main
13 .def _main; .scl 2; .type 32;
.endef
14 _main:
15 0008 55 pushl %ebp
16 0009 89E5 movl %esp, %ebp
17 000b 83EC18 subl $24, %esp
18 000e 83E4F0 andl $-16, %esp
19 0011 B8000000 movl $0, %eax
19 00
20 0016 83C00F addl $15, %eax
21 0019 83C00F addl $15, %eax
22 001c C1E804 shrl $4, %eax
23 001f C1E004 sall $4, %eax
24 0022 8945FC movl %eax, -4(%ebp)
25 0025 8B45FC movl -4(%ebp), %eax
26 0028 E8000000 call __alloca
26 00
27 002d E8000000 call ___main
27 00
28 0032 C7442408 movl $3, 8(%esp)
28 03000000
29 003a C7442404 movl $2, 4(%esp)
29 02000000
30 0042 C7042401 movl $1, (%esp)
30 000000
31 0049 E8B2FFFF call _function
31 FF
32 004e C9 leave
33 004f C3 ret
C:\Documents and Settings\Ross\My Documents\testing>
Here we see an output listing produced by the assembler. (Its name is GAS, because it is Gnu's version of the classic *nix assembler as. There's humor there somewhere.)
Each line has most of the following fields: a line number, an address in the current section, bytes stored at that address, and the source text from the assembly source file.
The addresses are offsets into that portion of each section provided by this module. This particular module only has content in the .text section which stores executable code. You will typically find mention of sections named .data and .bss as well. Lots of other names are used and some have special purposes. Read the manual for the linker if you really want to know.
It will be better to try the -fno-stack-protector flag with gcc to disable the canary and see your results.
I'd like to add that for simple stuff, GCC's assembly output is often easier to read if you turn on a little optimization. Here's the sample code again...
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
/* corrected calling convention of main() */
int main() {
function(1,2,3);
return 0;
}
this is what I get without optimization (OSX 10.6, gcc 4.2.1+Apple patches)
.globl _function
_function:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $36, %esp
call L4
"L00000000001$pb":
L4:
popl %ebx
leal L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax
movl (%eax), %eax
movl (%eax), %edx
movl %edx, -12(%ebp)
xorl %edx, %edx
leal L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax
movl (%eax), %eax
movl -12(%ebp), %edx
xorl (%eax), %edx
je L3
call ___stack_chk_fail
L3:
addl $36, %esp
popl %ebx
leave
ret
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)
call _function
movl $0, %eax
leave
ret
Whew, one heck of a mouthful! But look what happens with -O on the command line...
.text
.globl _function
_function:
pushl %ebp
movl %esp, %ebp
leave
ret
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
movl $0, %eax
leave
ret
Of course, you do run the risk of your code being rendered completely unrecognizable, especially at higher optimization levels and with more complicated stuff. Even here, we see that the call to function has been discarded as pointless. But I find that not having to read through dozens of unnecessary stack spills is generally more than worth a little extra scratching my head over the control flow.

Resources