Search code examples
assemblyarmmachine-codeimmediate-operand

How to encode immediate value in arm?


Suppose there is an inst like this:

add  ip, ip, #0x5000

the machine code is

05 CA 8C E2

and

E2 8C CA 05 = 11100010100011001100 1010 00000101
imm = rotate_right(101B, 1010B*2) = 0x5000

But if we know 0x5000, how can we get 101000000101? Is this reverse convert one-to-one correspondence? Thanks.


Solution

  • From the ARM ARM:

    ADD adds two values. The first value comes from a register. The second value can be either an immediate value or a value from a register, and can be shifted before the addition.

    The immediate value you're seeing is being shifted. Bits 11:0 of your instruction are the shifter operand - in your case: 0xA05.

    Later in the docs, that addressing mode is described:

    The <shifter_operand> value is formed by rotating (to the right) an 8-bit immediate value to any even bit position in a 32-bit word.

    So your specific shifter operand means 0x05 rotated right by (2 * 10) bits.

    You have a few choices if you're doing the instruction encoding. For example:

    0xA05 // rotate 0x05 right by 20
    0xB14 // rotate 0x14 right by 22
    0xC50 // rotate 0x50 right by 24
    

    I hand encoded them to disassemble:

    $ xxd -r > example
    00 05 CA 8C E2 14 CB 8C E2 50 CC 8C E2
    $ arm-none-eabi-objdump -m arm -b binary -D example
    
    example:     file format binary
    
    
    Disassembly of section .data:
    
    00000000 <.data>:
       0:   e28cca05    add ip, ip, #20480  ; 0x5000
       4:   e28ccb14    add ip, ip, #20480  ; 0x5000
       8:   e28ccc50    add ip, ip, #20480  ; 0x5000
    

    Here's a simple program that can find the encodings:

    #include <stdio.h>
    #include <stdlib.h>
    #include <inttypes.h>
    
    int main(int argc, char **argv)
    {
        uint32_t encode = strtoul(argv[1], NULL, 0);
        int rotate;
    
        for (rotate = 0; rotate < 32; rotate += 2)
        {
            // print an encoding if the only significant bits 
            // fit into an 8-bit immediate
            if (!(encode & ~0xffU))
            {
                printf("0x%X%02X\n", rotate/2, encode);
            }
    
            // rotate left by two
            encode = (encode << 2) | (encode >> 30);
        }
        return 0;
    }
    

    And an example run for your case:

    $ ./example 0x5000
    0xA05
    0xB14
    0xC50