Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARM Switch Statement Branching Incorrect #967

Closed
deadly-platypus opened this issue May 31, 2018 · 4 comments
Closed

ARM Switch Statement Branching Incorrect #967

deadly-platypus opened this issue May 31, 2018 · 4 comments

Comments

@deadly-platypus
Copy link

deadly-platypus commented May 31, 2018

The code in [1] incorrectly branches when compiled with the following command on Linux (assuming the code is in a file called sprintf.c):

arm-linux-gnueabi-gcc -o sprintf-arm -c sprintf.c

The bug can be triggered by running the Python code contained in [2]. When the code is run, the output contains the following snippet:

Executing instruction at 0x00000078
Executing instruction at 0x0000007c
Executing instruction at 0x00000080
Executing instruction at 0x000000cc
Executing instruction at 0x00000148
Executing instruction at 0x0000014c

The first three instructions are for the switch statement. However, instruction 0xcc is the branch instruction at the end of the case 1 block. I would expect the output to be the following:

Executing instruction at 0x00000078
Executing instruction at 0x0000007c
Executing instruction at 0x00000080
Executing instruction at 0x00000100

This is because instruction 0x100 is the start of the case 4 block, which the Python code sets up with the line uc.reg_write(uarm.UC_ARM_REG_R2, 4). If you compile [1] with arm-linux-gnueabi-gcc -o sprintf-arm-exe -DINCLUDE_MAIN sprintf.c, and run it in QEMU, the output is correct. I am using the pip version of unicorn for the python bindings.

$ pip show unicorn
Name: unicorn
Version: 1.0.1
Summary: Unicorn CPU emulator engine
Home-page: http://www.unicorn-engine.org
Author: Nguyen Anh Quynh
Author-email: [email protected]
License: UNKNOWN
Location: /usr/local/lib/python2.7/dist-packages
Requires: 
Required-by: 

Please let me know if there is more information I can provide.

[1]

#include <stddef.h>

/* Purposefully simplify sprintf for testing purposes.
 * Format string is expected to have only %d and in is
 * expected to be [0,5].
 */
int sprintf_simple(char* str, const char* fmt, int in) {
    int i = 0;
    int j = 0;
    while(fmt[i] != '\0') {
        if(fmt[i] == '%') {
            /*if(in == 0) { str[j] = '0'; }
            else if (in == 1) { str[j] = '1'; }
            else if (in == 2) { str[j] = '2'; }
            else if (in == 3) { str[j] = '3'; }
            else if (in == 4) { str[j] = '4'; }
            else if (in == 5) { str[j] = '5'; }
            else { str[j] = '_'; }*/
            switch(in) {
                case 0:
                    str[j] = '0';
                    break;
                case 1:
                    str[j] = '1';
                    break;
                case 2:
                    str[j] = '2';
                    break;
                case 3:
                    str[j] = '3';
                    break;
                case 4:
                    str[j] = '4';
                    break;
                case 5:
                    str[j] = '5';
                    break;
                default:
                    str[j] = '_';
                    break;
            }

            /* Skip over %d */ 
            i += 2;
            j++;
            continue;
        }
        str[j++] = fmt[i++];
    }
    str[j] = '\0';
    return j + 1;
}

#ifdef INCLUDE_MAIN
#include <stdio.h>
int main(int argc, char** argv) {
    char* fmt = "test %d";
    char s[12];
    
    printf("sprintf returned %d\n", sprintf_simple(s, fmt, 2));
    printf("s = '%s'\n", s);
    return 0;
}
#endif

[2]

import unicorn
import unicorn.arm_const as uarm


def code_hook(uc, address, size, user_data):
    print "Executing instruction at 0x{:08x}".format(address)


if __name__ == "__main__":
    uc = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_ARM)
    with open("sprintf-arm", "rb") as fp:
        binary = fp.read()

    uc.mem_map(0, 5 * 1024 * 1024)
    uc.mem_write(0, binary)
    uc.hook_add(unicorn.UC_HOOK_CODE, code_hook)
    uc.reg_write(uarm.UC_ARM_REG_SP, 0x270000)
    uc.mem_write(0x250000, "test: %d")
    # This value should be the start of sprint_simple
    uc.reg_write(uarm.UC_ARM_REG_PC, 0x34)
    uc.reg_write(uarm.UC_ARM_REG_R0, 0x260000)
    uc.reg_write(uarm.UC_ARM_REG_R1, 0x250000)
    uc.reg_write(uarm.UC_ARM_REG_R2, 4)
    uc.reg_write(uarm.UC_ARM_REG_R3, 0)
    uc.reg_write(uarm.UC_ARM_REG_R4, 0)
    uc.reg_write(uarm.UC_ARM_REG_R5, 0)
    uc.reg_write(uarm.UC_ARM_REG_R6, 0)
    uc.reg_write(uarm.UC_ARM_REG_R7, 0)
    uc.reg_write(uarm.UC_ARM_REG_R8, 0xFFFFFFFF)
    uc.reg_write(uarm.UC_ARM_REG_R9, 0xFFFFFFFF)
    uc.reg_write(uarm.UC_ARM_REG_R10, 0xFFFFFFFF)
    uc.reg_write(uarm.UC_ARM_REG_R11, 0xFFFFFFFF)
    uc.reg_write(uarm.UC_ARM_REG_LR, 0xFFFF)

    uc.emu_start(uc.reg_read(uarm.UC_ARM_REG_PC), 0xFFFE, timeout=5000, count=0)
@deadly-platypus
Copy link
Author

Also, if you comment out the switch statement, and uncomment the equivalent if-else statements, then the code behaves as intended.

@deadly-platypus
Copy link
Author

Finally, version 1.0.2 still exhibits this incorrect behavior.

@dpgeorge
Copy link

dpgeorge commented Jun 1, 2018

If the generated ARM assembly code is using ITE instructions then this could be related to #853, and see #880 for a work around patch.

@deadly-platypus
Copy link
Author

Thanks for the info. However, the disassembly of the switch code doesn't show any ITE instructions.

@wtdcode wtdcode closed this as completed Oct 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants