I think I need to paste the full code although it looks long.
I write a simple code for test.
#include <stdio.h>
int funadd(int a, int b){
int x = 0;
x = a + b;
return x;
}
int fun(int a, int b){
int y = 17;
int returnvalue = 0;
returnvalue = funadd(a, b);
returnvalue = returnvalue - y;
return returnvalue;
}
int main(){
int a = 32;
int b = 24;
int c = 0;
c = fun(a, b);
printf("%dn", c);
return c;
}
After assembly:
.file 1 "testfuncall.c"
.section .mdebug.abi32
.previous
.nan legacy
.module fp=xx
.module nooddspreg
.abicalls
.text
.align 2
.globl funadd
.set nomips16
.set nomicromips
.ent funadd
.type funadd, @function
funadd:
.frame $fp,24,$31 # vars= 8, regs= 1/0, args= 0, gp= 8
.mask 0x40000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
addiu $sp,$sp,-24
sw $fp,20($sp)
move $fp,$sp
sw $4,24($fp)
sw $5,28($fp)
sw $0,8($fp)
lw $3,24($fp)
lw $2,28($fp)
addu $2,$3,$2
sw $2,8($fp)
lw $2,8($fp)
move $sp,$fp
lw $fp,20($sp)
addiu $sp,$sp,24
jr $31
nop
.set macro
.set reorder
.end funadd
.size funadd, .-funadd
.align 2
.globl fun
.set nomips16
.set nomicromips
.ent fun
.type fun, @function
fun:
.frame $fp,40,$31 # vars= 8, regs= 2/0, args= 16, gp= 8
.mask 0xc0000000,-4
.fmask 0x00000000,0
.set noreorder
.cpload $25
.set nomacro
addiu $sp,$sp,-40
sw $31,36($sp)
sw $fp,32($sp)
move $fp,$sp
.cprestore 16
sw $4,40($fp)
sw $5,44($fp)
li $2,17 # 0x11
sw $2,24($fp)
sw $0,28($fp)
lw $5,44($fp)
lw $4,40($fp)
lw $2,%got(funadd)($28)
move $25,$2
.reloc 1f,R_MIPS_JALR,funadd
1: jalr $25
nop
lw $28,16($fp)
sw $2,28($fp)
lw $3,28($fp)
lw $2,24($fp)
subu $2,$3,$2
sw $2,28($fp)
lw $2,28($fp)
move $sp,$fp
lw $31,36($sp)
lw $fp,32($sp)
addiu $sp,$sp,40
jr $31
nop
.set macro
.set reorder
.end fun
.size fun, .-fun
.rdata
.align 2
$LC0:
.ascii "%d1200"
.text
.align 2
.globl main
.set nomips16
.set nomicromips
.ent main
.type main, @function
main:
.frame $fp,48,$31 # vars= 16, regs= 2/0, args= 16, gp= 8
.mask 0xc0000000,-4
.fmask 0x00000000,0
.set noreorder
.cpload $25
.set nomacro
addiu $sp,$sp,-48
sw $31,44($sp)
sw $fp,40($sp)
move $fp,$sp
.cprestore 16
li $2,32 # 0x20
sw $2,24($fp)
li $2,24 # 0x18
sw $2,28($fp)
sw $0,32($fp)
lw $5,28($fp)
lw $4,24($fp)
lw $2,%got(fun)($28)
move $25,$2
.reloc 1f,R_MIPS_JALR,fun
1: jalr $25
nop
lw $28,16($fp)
sw $2,32($fp)
lw $5,32($fp)
lw $2,%got($LC0)($28)
addiu $4,$2,%lo($LC0)
lw $2,%call16(printf)($28)
move $25,$2
.reloc 1f,R_MIPS_JALR,printf
1: jalr $25
nop
lw $28,16($fp)
lw $2,32($fp)
move $sp,$fp
lw $31,44($sp)
lw $fp,40($sp)
addiu $sp,$sp,48
jr $31
nop
.set macro
.set reorder
.end main
.size main, .-main
.ident "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
I realize after each function call, there is a lw $28,16($fp)
instruction. But I don’t see any code that would have stored a value there first in either the caller or callee.
I can read MIPS assembly. I know that lw
is load word, and how $fp and $sp are frame pointer and stack pointer.
I just can’t understand how it makes sense to load anything from 16($fp)
; it seems there is an uninitialized space.
I know $28
is $gp
, and can see it being used as a GOT pointer to load function addresses before calls, but it seems nothing initializes that register either before its used in functions.
Does the MIPS calling convention require $28
to already be pointing at the GOT on function entry?
2
Answers
I think I know why.
I have ignored the
.cprestore 16
automatically because I regard it as useless. But it turns out to emit actual instructions, not just metadata, related to the$gp
register..cprestore 16
will expand tosw $gp,16(sp)
. It is used together with.cpload $25
and other necessary code. Specifically, you can read the link https://www.linux-mips.org/wiki/PIC_codelw
is a “load word” instruction — it loads a word (4 bytes or 32 bits) from memory into a register.$28
is the destination register (also sometimes referred to a$gp
), and16($fp)
is the address to load from — 16 bytes into the frame ($fp
is the frame pointer register, and 16 is added to it to get the address to load from).The “frame” is generally used to hold local variables of the function –when a function starts, it allocates a frame on the stack by subtracting a constant from
$sp
, then stores the caller’s$fp
value somewhere in there and copies$sp
to$fp
so it points at this newly allocated frame. It then reads and writes local data into/from the frame with load(l
) and store(s
) instructions.If you had compiled with optimization, GCC would keep locals in registers when possible instead of wasting huge numbers of instructions storing/reloading them to the stack. And would access stack memory relative to the stack pointer, instead of spending an instruction to set up
$fp
as a traditional frame pointer. Un-optimized code doesn’t look anything like what a human would write by hand, but optimized code sometimes does.