It is known that %rsp points to the top of the stack frame and %rbp points to the base of the stack frame. Then I can't understand why %rbp is 0x0 in this piece of code:
(gdb) x/4xg $rsp
0x7fffffffe170: 0x00000000004000dc 0x0000000000000010
0x7fffffffe180: 0x0000000000000001 0x00007fffffffe487
(gdb) disas HelloWorldProc
Dump of assembler code for function HelloWorldProc:
=> 0x00000000004000b0 <+0>: push %rbp
0x00000000004000b1 <+1>: mov %rsp,%rbp
0x00000000004000b4 <+4>: mov $0x1,%eax
0x00000000004000b9 <+9>: mov $0x1,%edi
0x00000000004000be <+14>: movabs $0x6000ec,%rsi
0x00000000004000c8 <+24>: mov $0xd,%edx
0x00000000004000cd <+29>: syscall
0x00000000004000cf <+31>: leaveq
0x00000000004000d0 <+32>: retq
End of assembler dump.
(gdb) x/xg $rbp
0x0: Cannot access memory at address 0x0
And why is it "saving" (pushing) %rbp to the stack if it points to nothing?
RBP is a general-purpose register, so it can contain any value that you (or your compiler) wants it to contain. It is only by convention that RBP is used to point to the procedure frame. According to this convention, the stack looks like this:
Low |====================|
addresses | Unused space |
| |
|====================| ← RSP points here
↑ | Function's |
↑ | local variables |
↑ | | ↑ RBP - x
direction |--------------------| ← RBP points here
of stack | Original/saved RBP | ↓ RBP + x
growth |--------------------|
↑ | Return pointer |
↑ |--------------------|
↑ | Function's |
| parameters |
| |
|====================|
| Parent |
| function's data |
|====================|
| Grandparent |
High | function's data |
addresses |====================|
As such, the boilerplate prologue code for a function is:
push %rbp
mov %rsp, %rbp
This first instruction saves the original value of RBP by pushing it onto the stack, and then the second instruction sets RBP to the original value of RSP. After this, the stack looks exactly like the one depicted above, in the beautiful ASCII art.
The function then does its thing, executing whatever code it wants to execute. As suggested in the drawing, it can access any parameters it was passed on the stack by using positive offsets from RBP (i.e., RBP+x), and it can access any local variables it has allocated space for on the stack by using negative offsets from RBP (i.e., RBP-x). If you understand that the stack grows downward in memory (addresses get smaller), then this offsetting scheme makes sense.
Finally, the boilerplate epilogue code to end a function is:
leaveq
or, equivalently:
mov %rbp, %rsp
pop %rbp
This first instruction sets RSP to the value of RBP (the working value used throughout the function's code), and the second instruction pops the "original/saved RBP" off the stack, into RBP. It is no coincidence that this is precisely the opposite of what was done in the prologue code we looked at above.
Note, though, that this is merely a convention. Unless required by the ABI, the compiler is free to use RBP as a general-purpose register, with no relation to the stack pointer. This works because the compiler can just calculate the required offsets from RSP at compile time, and it is a common optimization, known as "frame pointer elision" (or "frame pointer omission"). It is especially common in 32-bit mode, where the number of available general-purpose registers is extremely small, but you'll sometimes see it in 64-bit code, too. When the compiler has elided the frame pointer, it doesn't need the prologue and epilogue code to manipulate it, so this can be omitted, too.
The reason you see all of this frame-pointer book-keeping is because you're analyzing unoptimized code, where the frame pointer is never elided because having it around often makes debugging easier (and since execution speed is not a significant concern).
The reason why it RBP is 0 upon entry to your function appears to be a peculiarity of GDB, and not something that you really need to concern yourself with. As Shift_Left notes in the comments, GDB under Linux pre-initializes all registers (except RSP) to 0 before handing off control to an application. If you had run this program outside of the debugger, and simply printed the initial value of RBP to stdout, you'd see that it would be non-zero.
But, again, the exact value shouldn't matter to you. Understanding the schematic drawing of the call stack above is the key. Assuming that frame pointers have not been elided, the compiler has no idea when it generates the prologue and epilogue code what value RBP will have upon entry, because it doesn't know where on the call stack the function will end up being called.
Related
TL;DR
How can I modify the stack while using ret or achieving similar effect while using something else?
Hello world,
I am trying to make a compiler for my language,
currently everything is inlined and it makes
the compilation slow for some steps so today I decided
to try to optimise it using functions, though
it keeps segfaulting, then I realised
This seems to not work:
;; main.s
BITS 64
segment .text
global _start
exit:
mov rax, 60 ;; Linux syscall number for exit
pop rdi ;; Exit code
syscall
ret
write:
mov rax, 1 ;; Linux syscall number for write
mov rdi, 1 ;; File descriptor (1 = stdout)
pop rsi ;; Pointer to string
pop rdx ;; String length
syscall
ret
_start:
mov rax, msg_len
push rax
mov rax, msg
push rax
call write
mov rax, 0
push rax
call exit
segment .data
msg: db "Hello, world!", 10
msg_len: equ $-msg
My output for this is.... questionable:
$ nasm -felf64 main.s
$ ld -o main main.s
$ ./main
PHello, world!
# # #$# #+ #2 #main.sexitwritemsgmsg_len__bss_start_edata_end.symtab.strtab.shstrtab.text.data9! # !77!'Segmentation fault
$? (exit code) is 139 (segfault)
While all inlined all works:
;; main1.s
BITS 64
segment .text
global _start
_start:
mov rax, msg_len
push rax
mov rax, msg
push rax
mov rax, 1 ;; Linux syscall number for write
mov rdi, 1 ;; File descriptor (1 = stdout)
pop rsi ;; Pointer to string
pop rdx ;; String length
syscall
mov rax, 0
push rax
mov rax, 60 ;; Linux syscall number for exit
pop rdi ;; Exit code
syscall
segment .data
msg: db "Hello, world!", 10
msg_len: equ $-msg
My output is completely normal:
$ nasm -felf64 main1.s
$ ld -o main1 main1.o
$ ./main1
Hello, world!
$? (exit code) is 0 (as specified in assembly, meaning success)
So now I'm here confused as I am a newbie
at assembly what to do, even though I found related
solutions like
NASM push before ret
I am still confused how to take that in...
Is there a way I can do it or am I stuck with inlining? Should I maybe switch assemblers all together from nasm to something else?
Thanks in advance
tl;dr
Remember that call is technically a push rip, and ret is technically a pop rip, so you pretty much messed up your stack in your example because you inadvertently pop it in the wrong spot.
More of an answer
Although you should probably properly learn how calling conventions work, I'm going to attempt an answer to briefly "soften" the idea, and for the fun of learning.
Abstractly speaking, in order to have functions, you must have something called stack frames, or else you'd have a pretty hard time managing local variables and getting ret to work. On x86_64, a stack frame is pretty much composed of a few things, in order.
The function arguments, if there are any0,
If some arguments were passed in registers, this may be omitted.
the return address,
The call instruction will push this onto the stack.
It's on you to make sure the ret instruction will pop this off the stack.
optionally a frame pointer,
If your stack grows by a dynamic amount, this can keep track of the start of the frame.
Otherwise, if you know the stack size ahead of time, it's optional.
and then your local state on the stack.
As long as execution stays within your little assembly space, you are technically free to pass arguments however you want1 as long as you are aware of how instructions like call and ret manipulate the stack. The simplest way, in my opinion, is to make it sort of stack-based, so that your compiler would not need to worry about register allocation as much2.
To keep things simple, I'd suggest using something like the x86 convention but applied to x86_64, as you seem to be using 64-bit code. That is to say, the caller function would push all of its arguments onto the stack (usually in reverse order), and then call the callee function. For example, for a 3-argument function, your stack would end up looking something like this (beware that the top of the stack is actually on the bottom).
+----------------+
| argument 2 |
+----------------+
| argument 1 |
+----------------+
| argument 0 |
+----------------+
| return address |
+----------------+
| local state |
| ... |
+----------------+
Also, I noticed that you never really made use of the rsp register. Depending on the design of your compiler, you technically could get away with this. Stack machines like the JVM rely solely on pushes and pops, anyway, I believe. As long as your pushes and pops match (especially call and ret, which act as a special push and pop), you should be fine.
0 Windows actually allocates at least an extra 32 bytes here for argument spilling, but you can probably ignore that in this case.
1 There are specific calling conventions that dictate how parameters are passed from caller to callee and back. Beyond your programming exercise, I highly recommend reading about how they work, so that your compiler can output code that can easily be called by and easily call functions that weren't emitted by your compiler, or go the Forth way as Nate mentioned.
2 goto 1
I saw the following rules from NASM's document:
The stack pointer %rsp must be aligned to a 16-byte boundary before making a call. Fine, but the process of making a call pushes the return address (8 bytes) on the stack, so when a function gets control, %rsp is not aligned. You have to make that extra space yourself, by pushing something or subtracting 8 from %rsp.
And I have a snippet of NASM assembly code as below:
The %rsp should be at the boundary of 8-bytes before I call the function "inc" in "_start" which violates the rules described in NASM's document. But actually, everything is going on well. So, how can I understand this?
I built this under Ubuntu 20.04 LTS (x86_64).
global _start
section .data
init:
db 0x2
section .rodata
codes:
db '0123456789abcdef'
section .text
inc:
mov rax, [rsp+8] ; read param from the stack;
add rax, 0x1
ret
print:
lea rsi, [codes + rax]
mov rax, 1
mov rdi, 1
mov rdx, 1
syscall
ret
_start:
; enable AC check;
pushf
or dword [rsp], 1<<18
popf
mov rdi, [init] ; move the first 8 bytes of init to %rdi;
push rdi ; %rsp -> 8 bytes;
call inc
pop r11 ; clean stack by the caller;
call print
mov rax, 60
xor rdi, rdi
syscall
The ABI is a set of rules for how functions should behave to be interoperable with each other. Each of the rules on one side are paired with allowed assumptions on the other. In this case, the rule about stack alignment for the caller is an allowed assumption about stack alignment for the callee. Since your inc function doesn't depend on 16-byte stack alignment, it's fine to call that particular function with a stack that's only 8-byte aligned.
If you're wondering why it didn't break when you enabled AC, that's because you're only loading 8-byte values from the stack, and the stack is still 8-byte aligned. If you did sub rsp, 4 or something to break 8-byte alignment too, then you would get a bus error.
Where the ABI becomes important is when the situation isn't one function you wrote yourself in assembly calling another function you wrote yourself in assembly. A function in someone else's library (including the C standard library), or one that you compiled from C instead of writing in assembly, is within its rights to do movaps [rsp - 24], xmm0 or something, which would break if you didn't properly align the stack before calling it.
Side note: the ABI also says how you're supposed to pass parameters (the calling convention), but you're just kind of passing them wherever. Again, fine from your own assembly, but they'll definitely break if you try to call them from C.
This is the assembly code from my bomb lap question, I am stuck in phase2;
The bomb lab require us to find out the correct input based on assembly code or it will exploded.
From <+20> I know that %rbp -0x30(48) == 0 or it will call <+32> and explode the bomb; so %rbp = 48(DEC)
After that(+26) %rbp - 0x2c(44) must equal 1 or it will explode the bomb...
But since %rbp = 48, the bomb will explode anywhere so I am confuse now...
I think I misunderstand the compl , je/jne or how to calculate these things...
-0x30(%ebp) doesn't mean to use the value %ebp - 0x30. It's a memory address to read from. The instruction (cmpl) has an l suffix, so it's dealing with a 4 byte quantity. So what's actually happening is that it reads a 4 byte number from the address %ebp - 0x30 and checks whether it's zero.
(The $ prefix means it's an immediate value, not an address. This is why 0x0 is taken literally and not dereferenced.)
You don't need to know value of rbp to defuse the bomb, it's used always in relative way to address certain portion of memory. It's set at the start by the sequence:
pushq %rbp
movq %rsp, %rbp
pushq %r12
pushq %rbx
subq $0x20, %rsp
Which does push old rbp value into stack (to preserve it), then sets rbp to current stack pointer value (rsp). So (%rbp) contains the old rbp now. Then another two registers r12 and rbx are preserved by pushing them into stack (which makes now rsp == rbp - 16). And then rsp is adjusted one more time by subtracting 32, i.e. rsp == rbp - 48.
This is common pattern how to allocate memory space for local variables, i.e. any further push instruction will use memory below rbp - 48. Memory from rbp - 48 up to rbp - 17 (inc.) is undefined, free to use as "local variables" memory. Then at rbp - 16 is stored in 8 bytes original rbx value, at rbp - 8 is stored original r12 value, and at rbp is stored old rbp. (I mean all the "rbp - x" to be used as memory addresses to address values in the memory)
Then your code calls input 6 numbers, which I guess means 32 bit integers (judging by the following code), so it provides it with address rbp - 48. 6*4 = 24 => the inputted values will be stored in memory from address rbp - 48 up to (incl.) rbp - 25.
Note the gap of "unused memory" from rbp - 24 up to rbp - 17, that's another 8 bytes of spare local storage, not used by the code you posted, that's very likely padding added by compiler to make the rsp correctly aligned before callq read_six_numbers.
So basically you don't need to know where exactly rsp/rbp points to, it's pointing to some valid stack memory (either that, or the code will crash, and the bomb will NOT explode ... a bit weird design :)))). You can pick any arbitrary value, like 0x8000 for rsp at start, and simulate the run with that. (i.e. the <+11> leaq -0x30,(%rbp), %rsi is then rsi = 0x7FC8; (0x8000 - 8 - 0x30).
Rest of what -x(%rbp) means in various instructions (pay attention to the lea vs <any other instruction> semantics difference of "memory operand" usage, the lea does only the memory address calculation, while other instructions just start by that, using the calculated address to access actual value stored in memory) is described in the other answer + comment. Plus use the x86 instruction reference guide and read through some tutorials few more times, until it will make sense.
I'm viewing the stack at the beginning of main, but the ebp of main is missing.
I declared a variable to check where will it's located on the stack, it turns out that there is zeros between this variable and the return address to n __libc_start_main !
System I'm using
I'm using fedora Linux 3.1.2-1.fc16.i686
ASLR is disabled.
Debugging with GDB.
Here's the code :
void main(){
char ret ='a';
}
Register information:
(gdb)
eax 0x1 1
ecx 0xbffff5f4 -1073744396
edx 0xbffff584 -1073744508
ebx 0x2dbff4 2998260
esp 0xbffff554 0xbffff554
**ebp 0xbffff558 0xbffff558**
esi 0x0 0
edi 0x0 0
eip 0x804839a 0x804839a <main+6>
stack
(gdb) x/8xw $esp
0xbffff554: 0x00000000(local var) 0x00000000(missing ebp!) 0x0014d6b3(return to libc_start) 0x00000001
0xbffff564: 0xbffff5f4 0xbffff5fc 0x00131fc4 0x0000082d
The only thing that I can think of is that the function prologue of the libc_start_main is not pushing the main's ebp for some reason !
Edit 1:
-compiled without Opatmization just (gcc -ggdb file file.c)
Assembly of main ( gcc version 4.6.2 20111027 )
push %ebp
mov %esp,%ebp
sub $0x10,%esp
movb $0x61,-0x1(%ebp)
leave
ret
A break point at the local variable to view the stack shows the same thing the variable followed by zeros then the return to libc_start
There's no requirement for a particular calling convention to be used in the assembly code generated by a compiler. That's why it's called a convention rather than a requirement :-)
In any case, you need to keep in mind that the 'normal' x86 calling convention for C requires the function itself to handle set-up and tear-down of the stack frame. In other words, this is the responsibility of main rather than the startup code (the code that generally runs before your main to set up the C runtime environment such as stack setup, creation of argc/argv, any library pre-initialisation and so on).
Additionally, the ebp pushed on to the stack is the previous value of ebp before the current stack frame is built.
Part of that build process for the current stack frame is the saving of the current ebp and then loading a new value into the ebp register to easily access passed parameters and locals.
You can see that by compiling your code snippet with gcc -S:
main:
pushl %ebp ; Push PREVIOUS ebp.
movl %esp, %ebp ; Load ebp for variable access.
subl $16, %esp ; Allocate space on stack.
movb $97, -1(%ebp) ; Store 'a' into variable.
leave ; Tear down frame and return.
ret
The first three lines and the last two are mirror images of each other, the set-up and tear-down code. There's a good chance in this case that the startup code had ebp set to zero, possibly because it didn't care - it doesn't have to worry about calling conventions other than to ensure argc and argv are there.
If you compile without optimization, you'll almost certainly find that ebp/rbp is in fact pushed pushed onto the stack and then set up based on esp/rsp. It is, however, done by main itself and not by libc as you appear to suggest.
Here is the assembly code produced by gcc 4.4.5:
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movb $97, -1(%rbp)
leave
ret
.cfi_endproc
If you compile with optimization options, you might find that the entire body of main is optimized away (gcc -O3):
main:
.LFB0:
.cfi_startproc
rep
ret
.cfi_endproc
Instead of guessing, why not look at the disassembly (e.g. in gdb) to see what happens in your particular case?
Also, even in the unoptimized case you have to actually execute the function prologue for the registers to be set up the way you expect.
Finally, you should not be surprised when you see apparent gaps between data on the stack, as the stack is subject to alignment:
-mpreferred-stack-boundary=num
Attempt to keep the stack boundary aligned to a 2 raised to num
byte boundary. If -mpreferred-stack-boundary is not specified, the
default is 4 (16 bytes or 128 bits).
If you are compiling for x86_64 then ebp/rbp is callee saved. That means that main() should save it if it needs to use it. If not then there is no requirement for the old register value to be saved by the either callee or the caller.
See section 3.2 of the AMD64 ABI for more information if you are interested.
Heyo,
I have written this very basic main function to experiment with disassembly and also to see and hopefully understand what is going on at the lower level:
int main() {
return 6;
}
Using gdb to disas main produces this:
0x08048374 <main+0>: lea 0x4(%esp),%ecx
0x08048378 <main+4>: and $0xfffffff0,%esp
0x0804837b <main+7>: pushl -0x4(%ecx)
0x0804837e <main+10>: push %ebp
0x0804837f <main+11>: mov %esp,%ebp
0x08048381 <main+13>: push %ecx
0x08048382 <main+14>: mov $0x6,%eax
0x08048387 <main+19>: pop %ecx
0x08048388 <main+20>: pop %ebp
0x08048389 <main+21>: lea -0x4(%ecx),%esp
0x0804838c <main+24>: ret
Here is my best guess as to what I think is going on and what I need help with line-by-line:
lea 0x4(%esp),%ecx
Load the address of esp + 4 into ecx. Why do we add 4 to esp?
I read somewhere that this is the address of the command line arguments. But when I did x/d $ecx I get the value of argc. Where are the actual command line argument values stored?
and $0xfffffff0,%esp
Align stack
pushl -0x4(%ecx)
Push the address of where esp was originally onto the stack. What is the purpose of this?
push %ebp
Push the base pointer onto the stack
mov %esp,%ebp
Move the current stack pointer into the base pointer
push %ecx
Push the address of original esp + 4 on to stack. Why?
mov $0x6,%eax
I wanted to return 6 here so i'm guessing the return value is stored in eax?
pop %ecx
Restore ecx to value that is on the stack. Why would we want ecx to be esp + 4 when we return?
pop %ebp
Restore ebp to value that is on the stack
lea -0x4(%ecx),%esp
Restore esp to it's original value
ret
I am a n00b when it comes to assembly so any help would be great! Also if you see any false statements about what I think is going on please correct me.
Thanks a bunch! :]
Stack frames
The code at the beginning of the function body:
push %ebp
mov %esp, %ebp
is to create the so-called stack frame, which is a "solid ground" for referencing parameters and objects local to the procedure. The %ebp register is used (as its name indicates) as a base pointer, which points to the base (or bottom) of the local stack inside the procedure.
After entering the procedure, the stack pointer register (%esp) points to the return address stored on the stack by the call instruction (it is the address of the instruction just after the call). If you'd just invoke ret now, this address would be popped from the stack into the %eip (instruction pointer) and the code would execute further from that address (of the next instruction after the call). But we don't return yet, do we? ;-)
You then push %ebp register to save its previous value somewhere and not lose it, because you'll use it for something shortly. (BTW, it usually contains the base pointer of the caller function, and when you peek that value, you'll find a previously stored %ebp, which would be again a base pointer of the function one level higher, so you can trace the call stack that way.) When you save the %ebp, you can then store the current %esp (stack pointer) there, so that %ebp will point to the same address: the base of the current local stack. The %esp will move back and forth inside the procedure when you'll be pushing and popping values on the stack or reserving & freeing local variables. But %ebp will stay fixed, still pointing to the base of the local stack frame.
Accessing parameters
Parameters passed to the procedure by the caller are "burried just uner the ground" (that is, they have positive offsets relative to the base, because stack grows down). You have in %ebp the address of the base of the local stack, where lies the previous value of the %ebp. Below it (that is, at 4(%ebp) lies the return address. So the first parameter will be at 8(%ebp), the second at 12(%ebp) and so on.
Local variables
And local variables could be allocated on the stack above the base (that is, they'd have negative offsets relative to the base). Just subtract N to the %esp and you've just allocated N bytes on the stack for local variables, by moving the top of the stack above (or, precisely, below) this region :-) You can refer to this area by negative offsets relative to %ebp, i.e. -4(%ebp) is the first word, -8(%ebp) is second etc. Remember that (%ebp) points to the base of the local stack, where the previous %ebp value has been saved. So remember to restore the stack to the previous position before you try to restore the %ebp through pop %ebp at the end of the procedure. You can do it two ways:
1. You can free only the local variables by adding back the N to the %esp (stack pointer), that is, moving the top of the stack as if these local variables had never been there. (Well, their values will stay on the stack, but they'll be considered "freed" and could be overwritten by subsequent pushes, so it's no longer safe to refer them. They're dead bodies ;-J )
2. You can flush the stack down to the ground and free all local space by simply restoring the %esp from the %ebp which has been fixed earlier to the base of the stack. It'll restore the stack pointer to the state it has just after entering the procedure and saving the %esp into %ebp. It's like loading the previously saved game when you've messed something ;-)
Turning off frame pointers
It's possible to have a less messy assembly from gcc -S by adding a switch -fomit-frame-pointer. It tells GCC to not assemble any code for setting/resetting the stack frame until it's really needed for something. Just remember that it can confuse debuggers, because they usually depend on the stack frame being there to be able to track up the call stack. But it won't break anything if you don't need to debug this binary. It's perfectly fine for release targets and it saves some spacetime.
Call Frame Information
Sometimes you can meet some strange assembler directives starting from .cfi interleaved with the function header. This is a so-called Call Frame Information. It's used by debuggers to track the function calls. But it's also used for exception handling in high-level languages, which needs stack unwinding and other call-stack-based manipulations. You can turn it off too in your assembly, by adding a switch -fno-dwarf2-cfi-asm. This tells the GCC to use plain old labels instead of those strange .cfi directives, and it adds a special data structures at the end of your assembly, refering to those labels. This doesn't turn off the CFI, just changes the format to more "transparent" one: the CFI tables are then visible to the programmer.
You did pretty good with your interpretation. When a function is called, the return address is automatically pushed to the stack, which is why argc, the first argument, has been pushed back to 4(%esp). argv would start at 8(%esp), with a pointer for each argument, followed by a null pointer. This function pushes the old value of %esp to the stack so that it can contain the original, unaligned value upon returned. The value of %ecx at return doesn't matter, which is why it is used as temporary storage for the %esp reference. Other than that, you are correct with everything.
Regarding your first question (where are stored the command line arguments), arguments to functions are right before ebp. I must say, your "real" main begins at < main + 10 >, where it pushes ebp and moves esp to ebp. I think that gcc messes everything up with all that leas just to replace the usual operations (addictions and subtractions) on esp before and after functions call. Usually a routine looks like this (simple function I did as an example):
0x080483b4 <+0>: push %ebp
0x080483b5 <+1>: mov %esp,%ebp
0x080483b7 <+3>: sub $0x10,%esp # room for local variables
0x080483ba <+6>: mov 0xc(%ebp),%eax # get arg2
0x080483bd <+9>: mov 0x8(%ebp),%edx # and arg1
0x080483c0 <+12>: lea (%edx,%eax,1),%eax # just add them
0x080483c3 <+15>: mov %eax,-0x4(%ebp) # store in local var
0x080483c6 <+18>: mov -0x4(%ebp),%eax # and return the sum
0x080483c9 <+21>: leave
0x080483ca <+22>: ret
Perhaps you've enabled some optimizations, which could make the code trickier.
Finally yes, the return value is stored in eax. Your interpretation is quite correct anyway.
The only thing I think that's outstanding from your original questions is why the following statements exist in your code:
0x08048381 <main+13>: push %ecx
0x08048382 <main+14>: mov $0x6,%eax
0x08048387 <main+19>: pop %ecx
The push and pop of %ecx at <main+13> and <main+19> don't seem to make much sense - and they don't really do anything in this example, but consider the case where your code invokes function calls.
There's no way for the system to guarantee that the calls to other functions - which will set up their own stack activation frames - won't reset register values. In fact they probably will. The code therefore sets up a saved register section on the stack where any registers used by the code (other than %esp and %ebp which are already saved though the regular stack setup) are stored in the stack before possibly handing control over to function calls in the "meat" of the current code block.
When these potential calls return, the system then pops the values off the stack to restore the pre-call register values. If you were writing assembler directly rather than compiling, you'd be responsible for storing and retrieving these register values, yourself.
In the case of your example code, however, there are no function calls - only a single instruction at <main+14> where you're setting the return value, but the compiler can't know that, and preserves its registers as usual.
It would be interesting to see what would happen here if you added C statements which pushed other values onto the stack after <main+14>. If I'm right about this being a saved register section of the stack, you'd expect the compiler to insert automatic pop statements prior to <main+19> in order to clear these values.