C & Arm assembly: emulate execution with QEMU
Emulate the execution of a sample program using C and Arm assembly using QEMU ( quick emulator)

C & Arm assembly: emulate execution with QEMU

‘An Arm programming example combined with C programming in an embedded device’

In this article , we are attempting to write and execute code for {Versatilepb/arm926ej-s } in a native Ubuntu OS system, by cross-compiling the code and emulating the execution using the QuickEmulator(QEMU)

Code can be found in my Github Arm_Generic repository, here: https://github.com/NikosMouzakitis/Arm_generic/tree/master/10

A short intoduction of the tools used / required to recreate the complete functionality shown.

Requirments:

  1. qemu
  2. arm-none-eabi
  3. gdb-multiarch

A quick description of what the program should do, it that, after entering the startup code(startup.s), execution switch to a C code file( test.c ) and at some point it is going to enter in an assembly written function that it is implementing positive integer division( division.s ). Soon after it returns an implemented function in C code ( print_uart0() ) will be called to actually print out the result of the positive division in the terminal emulated by qemu.


No alt text provided for this image

Above we are inspecting the Makefile, that will run the program.

In the first two commands, we do use the portable GNU assembler to generate the object files and as well we are declaring the cpu that will run it. Afterwards we do compile the test.c code for the same cpu. Linking occurs in the next command and copy and translation of object files in the 7th line. Finally qemu is called for the exact platform with features for memory and the binary.

Next we will have a look into the test.ld file used for linking.

No alt text provided for this image

We can see that our sections in the device’s memory start at the address 0x10000, followed by the .startup(of startup.s) after is the(division.s) then .text segment, .data and .bss segment.We do provide a 4 kb of stack memory and set the stack_top at 0x2000.

Next we will have a look in the startup.s file

We can see the declaration of the global _Reset that was also mentioned in the linker file above, and quick after executing stack_top address is loaded into the sp (stack_pointer_register) and we branch by linking (bl) in the c_entry function.(defined in the test.c C code file.)

The test.c C code file is displayed below.


// 10-program
//@author: Mouzakitis Nikolaos

//***********************************************************************

/*

In this program we are implementing an integer division

in ARM assembly and print out of the value with the function

introduced in folder 08/.

*/

//***********************************************************************


// Address in hardware for the UART peripheral.

volatile unsigned int * const UART0DR = (unsigned int *) 0x101f1000;


extern int division(int a, int b);

static char converted[16];

static char help[16];




// Function that will convert an integer into actually mapped in ASCII

// code characters to be displayed in the terminal peripheral.

void convert(int source)

{

  int tmp, i, j, ak, res;

  int num = 0;

  res = source;

  for(i = 0; i < 16; i++) {

    converted[i] = 0;

    help[i] = 0;

  }




//finding the last bit.

  ak = res % 10;

  num++;

  res = source / 10;

  converted[0] = ak;




  while(res > 0) {

    tmp = res % 10;

    converted[num] = tmp;

    num++;

    res = res/10;

  }


  for(i = 0; i < num; i++) {


    switch (converted[i]) {

    case 0: converted[i] = 48;

      break;

    case 1: converted[i] = 49;

      break;

    case 2: converted[i] = 50;

      break;

    case 3: converted[i] = 51;

      break;

    case 4: converted[i] = 52;

      break;

    case 5: converted[i] = 53;

      break;

    case 6: converted[i] = 54;

      break;

    case 7: converted[i] = 55;

      break;

    case 8: converted[i] = 56;

      break;

    case 9: converted[i] = 57;

      break;

  }

}

// inverting the characters written in the array in order to have the forward instead of backward number.

for(i = num-1; i >= 0; i--) {

  help[j] = converted[i];
  
  j++;
  
  }
  
}
  
static inline unsigned long asm_get_cpsr(void)
  
{
  
       unsigned long retval;
  
       __asm(" mrs r0, cpsr");
  
       __asm(" mov %0, r0"
       
       : "=r"(retval)
  
       : "0"(retval) );
  
  
       return retval;
  
}
  
  //print from uart0
void print_uart0(const char *s) {
  
  
  
  
      while(*s!='\0') {
  
         *UART0DR = (unsigned int) (*s);
  
         s++;
  
      }
  
  }
  
void c_entry()
  
{
  
      int a =8;
      
      int b =15;
      
      int res;
          
      res = division(a, b);
        
      convert(res);
      
      print_uart0(help);
      
}            


We see that after entering c_entry function, soon enough we will call division(), that is declared as extern int division(int, int) and the result will be converted( we are not going into details about how convert() works-it is obvious by the provided code). Instead we will talk about the next function called the print_uart0() before going into describing the division function.

After having a look in the manual of the board that we are running the code on, we need to find the exact address that refers to the Uart0(to print something in the terminal in qemu, so we can see the output we generate) we find out that the peripheral base address for the UART zero is 0x1F1000. And this is assignment we do on the UART0DR variable in our C code.

No alt text provided for this image

Picture taken from the reference manual : https://infocenter.arm.com/help/topic/com.arm.doc.ddi0287b/DDI0287B_arm926ejs_dev_chip_technical_reference_manual.pdf

In the function print_uart0() we are providing a char *, and till we reach a ‘\0’ character we are writting in the base address defined the characters one by one resulting the output in the Qemu terminal.

Now in the context of division() function, we have the division.s file where the function it is implemented in ARM assembly. The two arguments are passed into the register r0 and r1, while also the return value we intend to send back in the C code got to be before we branch back in the r0. The code follows :

No alt text provided for this image

Declaration of the global division and the most important things here are the cmp r5,r0 where if the integer in r5 is less than 0 we will branch to exit and follow the lr(link register ) to return back in the address of PC+4 before we entered the division() function back in the test.c .

The return value is acquired since is saved in the r0 register.

Demonstrating now, after we call make, in a new terminal we do run the command ‘ gdb-multiarch -quiet -x /usr/bin/com_file’ where in my case com_file consists of the commands :

/usr/bin/com_file


# com_file contents: 

layout next
layout next
layout next
layout next
target remote localhost:1234
file test.elf
b c_entry
continue        

where in the first four commands we just change the layout, afterwards we are connecting remotely, setting a breakpoint in the defined c_entry and all we need to do is press ‘s’ and ‘enter’ to do step by step debugging.

No alt text provided for this image


We can see after dividing 18 by 15 the output of the generated integer division is 1.


要查看或添加评论,请登录

Nikos Mouzakitis的更多文章

社区洞察

其他会员也浏览了