Chinaunix首页 | 论坛 | 博客
  • 博客访问: 351009
  • 博文数量: 67
  • 博客积分: 2550
  • 博客等级: 少校
  • 技术积分: 990
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-17 18:24
文章分类

全部博文(67)

文章存档

2011年(6)

2010年(2)

2009年(40)

2008年(19)

我的朋友

分类: LINUX

2008-12-30 14:42:49

Even though most of Embedded and Realtime programming is now carried out in high level languages, a good understanding of the generated assembly code really helps in debugging, performance analysis and performance tuning.

Here we present a series of articles describing C to assembly translation. We will be analyzing the code generated by a compiler targeting the Motorola 68000 processor family. The concepts learnt here can easily be applied to understand the generated code for any other processor-compiler combination.

In this article, we will discuss the assembly code generated for function calling, parameter passing and local variable management. Before we go any further we need to discuss a few things about the 68000 architecture.

68000 Basics

  • 68000 family processors contain 8 data registers (D0-D7) and 8 address registers (A0-A7).
  • The MOVE  instruction in 68000 assemblers has the source on the left side and destination on the right side.
  • Register D0 is used to return values to the calling function.
  • The stack in the 68000 family grows from higher address to lower address. Thus a push results in a decrement to the stack pointer. A pop results in an increment to the stack pointer.
  • Address register A7 is the stack pointer. The pre-decrement (i.e. decrement the register before use) addressing mode is used to implement a push. The post-increment addressing mode (i.e. use register and then increment) is used to implement a pop.
  • Most compilers use A6 as the frame pointer. The frame pointer serves as an anchor between the called and the calling function. 
  • When a function is called, the function first saves the current value of A6 on the stack. It then saves the value of the stack pointer in A6 and then decrements the stack pointer to allocate space for local variables. 
  • The frame pointer (A6) is used to access local variables and parameters. Local variables are located at a negative offset to the frame pointer. Parameters passed to the function are located at a positive offset to the frame pointer.
  • When the function returns, the frame pointer (A6) is copied into the stack pointer. This frees up the stack used for local variables. The value of A6 saved on the stack is restored. (This is the frame pointer for the function that called this function).

Function Calling

The following block shows the C code and the corresponding generated assembly code.

C Code

int CallingFunction(int x)
{
int y;
CalledFunction(1,2);
return (5);
}

void CalledFunction(int param1, int param2)
{
int local1, local2;
local1 = param2;
}

The generated assembly code is shown along with the corresponding C code.

Generated 68000 Assembly Code

int CallingFunction(int x)
{
int y;

* Reserving space for local variable y (4 bytes)
LINK A6, #-4
CalledFunction(1,2);
* Pushing the second parameter on the stack
MOVE.L #2, -(A7)
* Pushing the first parameter on the stack
MOVE.L #1, -(A7)
* Calling the CalledFunction()
JSR _CalledFunction
* Pop out the parameters after return
ADDQ.L #8, A7
return (5);
* Copy the returned value 5 into D0 (As a convention, D0 is used to pass
* return values)
MOVEQ.L #5, D0
}
* Freeing up the stack space taken by local variables
UNLK A6
* Return back to the calling function
RTS

void CalledFunction(int param1, int param2)
{
int local1, local2;

* Reserving space for local1, local2 (4 bytes each)
LINK A6, #-8
local1 = param2;
MOVE.L 12(A6), -4(A6)
}
* Freeing up the stack space taken by local variables
UNLK A6
* Return back to the calling function
RTS

Function Calling Sequence

The generated assembly code is best understood by tracing through the invocation of CalledFunction() from CallingFunction().

  1. CallingFunction() pushes values 2 followed by 1 on the stack. These values correspond to param2 and param1 respectively. (Note that pushing order is reverse of the declaration order.). This is implemented by the MOVE.L instructions that implement a push operation by using the predecrement mode on A7.
  2. CallingFunction() invokes the CalledFunction() by the JSR instruction. JSR pushes the return address on the stack and transfers control to CalledFunction().
  3. Called Function executes the LINK instruction. The link instruction performs the following operations:
    • Saves the CallingFunction()'s frame pointer on the stack (i.e. content of A6 is saved on the stack)
    • Moves the contents on the stack pointer (A7) into A6. This will serve as the frame pointer for CalledFunction().
    • Decrements the stack pointer by 8 to create space for the local variables local1 and local2.
  4. After the LINK instruction, code in the CalledFunction() accesses passed parameters by taking positive offsets from the frame pointer. Local variables are accessed by taking negative offsets from the frame pointer. (The above assembly code gives one example of such an access).
  5. Before the function ends UNLK is executed to undo the actions taken by the LINK instruction. This is done in the following steps:
    • Copy the contents of A6 to A7. This will free the stack entries allocated for local variables local1 and local2.
    • Pop the saved frame pointer from the stack. (This will make sure that the CallingFunction() gets its original frame pointer value on return).
  6. The processor now executes the RTS instruction. This instruction pops the return address from the stack and transfers control to the CallingFunction() at this address.
  7. The CallingFunction() now pops the parameters that were passed to the CalledFunction(). This is done by adding 8 to the stack pointer. 

CallingFunction() Stack Snapshot

The figure below shows the stack snapshot when executing CalledFunction() body, i.e. after the LINK instruction has been executed, but before the UNLK instruction has been executed.

阅读(1725) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~