1

I have this simple assembly for arm architecture 64 that will print "Hello World\n" from macro I made.

We know macro is analogy with function in high level programming.

So I want separate macro to another file. Here is my assembly code I made.

.data

.text
.globl _start

_start:

.macro print string
 b 2f // jump to label 2 forward (so it will not bother var)
 1: // label 1 (var declaration)
 .asciz "\string" // store argumen (string) in memory
 len_string = . - 1b // get len of string
 udf #0 // if i dont use this it will branch error :(
 2: // label 2 (main macro code)
 mov x0, #1 // file descriptor (1 means stdout)
 ldr x1, =1b // data to print (from label 1 back)
 ldr x2, =len_string //len of data
 mov x8, #0x40 // kernel convention 0x40 means writing
 svc #0 // SuperVisorCall kernel
.endm

print "Hello " // call macro
print "World\n" //call again

_exit: // exit code return 0
mov x0,#0
mov x8,#0x5d
svc #0

To verify it, compile and run that code. Save as hello.s and run this in aarch64 devices for example android or raspi

as hello.s -o hello.o && ld hello.o -o hello && ./hello

So from above code, I made macro named print with parameter string. But as you see I define macro in main program. I expect I can import that print in another source file. What should I do?

  • Does this answer your question? [Including header file in assembly file](https://stackoverflow.com/questions/60192138/including-header-file-in-assembly-file) – Morrison Chang Jun 02 '22 at 08:42
  • It seems yes but also i need to change the compiler to GCC. – Muhammad Ikhwan Perwira Jun 02 '22 at 08:47
  • 1
    A macro is not the counterpart to a function – the counterpart to an assembly macro is a macro in a high level language. The counterpart to the function is the function. – fuz Jun 02 '22 at 09:06

1 Answers1

2

The counterpart to a high-level function is an assembly function. The counterpart to an assembly macro is a macro or template in a high level language.

Modular Programming with Functions

The easiest way to write structured or modular code is to write functions. A function is very similar to what you wrote, but you need to return from it using a ret instruction. This print function takes the address of the string you want to print in x0 and its length in x1. It follows the AArch64 ABI and trashes registers x0, x1, x2, and x8.

        .text
        .type print, @function  // these two directives make the function
        .globl print            // callable from other object files.
print:  mov     x2, x1          // move length into place for syscall
        mov     x1, x0          // move string address into place for syscall
        mov     x0, #1          // print to stdout
        mov     x8, #0x40       // do a write system call
        svc     #0
        ret                     // return to caller

You can then call the function from any object file you like:

        .data
hello:  .ascii "hello world\n"
len=    .-hello

        .text
        ldr     x0, =hello      // load string address from literal pool
        mov     x1, #len        // load string length
        bl      print           // call our print function

Note that as each function uses the same lr register to keep track of the return address, you may need to save the lr register to the stack in functions that call other functions. As registers must be pushed onto the stack in pairs on arm64, I've pushed xzr as the second register. In practice, you'll probably want to push some other register that needs to be saved.

        str     lr, xzr, [sp, #-16]!  // push lr and xzr onto the stack
        ...
        ldr     x0, =hello            // load string address from literal pool
        mov     x1, #len              // load string length
        bl      print                 // call our print function
        ...
        ldr     lr, xzr, [sp], #16    // pop lr and xzr off the stack
        ret

Modular Programming with Macros

Your macro approach is almost correct. Here is a slightly improved version. Note how we use \@ to generate unique labels for the strings used. This allows us to not trash the numbered labels, permitting them to be used by code outside of the macro.

        .macro  print string    // define a macro print with argument string
        .pushsection .data      // temporarily go into .data               
str\@:  .ascii  "\string"       // define the string
len\@=  .-str\@                 // and its length

        .popsection             // go back to the previous section
        mov     x0, #1          // print to stdout
        ldr     x1, =str\@      // print our string
        mov     x2, #len\@      // of this length
        mov     x8, #0x40       // with a write system call
        svc     #0              // do the system call!
        .endm

If you want to invoke macros defined in other files, you have to include these file into the other files. This can be done with a .include directive. For example, suppose you have your macros in a file named macros.inc, you can include them like this:

        .include "macros.inc"       // include macros

Refer to the GNU as manual for details.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • So basically declaring ```function``` is just like putting ```label``` except added with ```ret```. And also you are using x0 and x1 as parameter instead of immediatelly. Unlike ```macro```, we can put string immadiately into its paramater. – Muhammad Ikhwan Perwira Jun 02 '22 at 12:26
  • @MuhammadIkhwanPerwira The important difference with functions is that the function exists only once in your program. With a macro, the code is copied each time you invoke the macro. – fuz Jun 02 '22 at 12:56
  • Okay I got it, according your explanation macro is expensive in memory managament, means more I call macro more memory that will wasted while function is not. – Muhammad Ikhwan Perwira Jun 03 '22 at 04:36
  • So I have combine macro and function, first I define function then call that function in macro. – Muhammad Ikhwan Perwira Jun 03 '22 at 05:43
  • 1
    @MuhammadIkhwanPerwira The memory waste for a simple macro is usually not critical, but think about what happens when you have functions calling functions vs macros calling macros. In the latter case, the lower level functions end up being instantiated lots of times. And some functions like e.g. a `printf` equivalent implemented as a macro would consume over 1 kB of memory each time it is used. Defining a macro to make it more convenient to call a function is definitely one way to do it. – fuz Jun 03 '22 at 09:23
  • Okay i want prove memory usage ```printf``` and hello world that we use with assembly. If ```time``` comand is used to be measure time execution, how about measuring memory? – Muhammad Ikhwan Perwira Jun 03 '22 at 09:41
  • @MuhammadIkhwanPerwira You can use the `size` command to check the size of a binary. Please note that due to dynamic linking, you'll not actually see the `printf` function in your binary as it is located in the libc. Also, the libc is very complex and printf depends on a lot of other functions. So the output will be very confusing. – fuz Jun 03 '22 at 10:14