I am using the IAR Embedded Workbench to write a C program to run on the Allwinner V831 SoC with an ARMv7 core. The SoC is on the MAIX-II Dock and I boot the system via an SD-Card.
I have a problem with the printf
function. In most cases it works just fine but if I call printf
in a subroutine which is called from main
and the printf
call is the last thing that happens in the function then the program completely crashes. Also interestingly this only happens if the subroutine is written in a separate file than the main
function.
I wrote two test functions:
void printf_no_fail() {
printf("no fail 1\n");
printf("no fail 2\n");
printf("no fail 3\n");
volatile int x = 3;
volatile int y = x + 7;
}
void printf_fail() {
printf("fail 1\n");
printf("fail 2\n");
printf("fail 3\n");
}
I call both these functions in main
. The first one runs perfectly fine and prints all three messages. The second one breaks the program: Only the first two messages are printed and then the program crashes which I know because other code that comes in the main
function is not executed.
I took a look at the assembler instructions in the list-file and there I did notice a difference.
1 #include <stdio.h>
2
\ In section .text, align 4, keep-with-next
3 void printf_no_fail() {
\ printf_no_fail:
\ 0x0 0xE92D'5000 PUSH {R12,LR}
4 printf("no fail 1\n");
\ 0x4 0x....'.... ADR R0,?_0
\ 0x8 0x....'.... BL printf
5 printf("no fail 2\n");
\ 0xC 0x....'.... ADR R0,?_1
\ 0x10 0x....'.... BL printf
6 printf("no fail 3\n");
\ 0x14 0x....'.... ADR R0,?_2
\ 0x18 0x....'.... BL printf
7 volatile int x = 3;
\ 0x1C 0xE3A0'0003 MOV R0,#+3
\ 0x20 0xE58D'0000 STR R0,[SP, #+0]
8 volatile int y = x + 7;
\ 0x24 0xE59D'3000 LDR R3,[SP, #+0]
\ 0x28 0xE283'C007 ADD R12,R3,#+7
\ 0x2C 0xE58D'C000 STR R12,[SP, #+0]
9 }
\ 0x30 0xE8BD'8001 POP {R0,PC}
10
\ In section .text, align 4, keep-with-next
11 void printf_fail() {
\ printf_fail:
\ 0x0 0xE92D'5000 PUSH {R12,LR}
12 printf("fail 1\n");
\ 0x4 0x....'.... ADR R0,?_3
\ 0x8 0x....'.... BL printf
13 printf("fail 2\n");
\ 0xC 0x....'.... ADR R0,?_4
\ 0x10 0x....'.... BL printf
14 printf("fail 3\n");
\ 0x14 0xE8BD'4002 POP {R1,LR}
\ 0x18 0x....'.... ADR R0,?_5
\ 0x1C 0x....'.... B printf
15 }
\ In section .text, align 4, keep-with-next
\ ?_0:
\ 0x0 0x6E 0x6F DC8 "no fail 1\012"
\ 0x20 0x66
\ 0x61 0x69
\ 0x6C 0x20
\ 0x31 0x0A
\ 0x00
\ 0xB DS8 1
\ In section .text, align 4, keep-with-next
\ ?_1:
\ 0x0 0x6E 0x6F DC8 "no fail 2\012"
\ 0x20 0x66
\ 0x61 0x69
\ 0x6C 0x20
\ 0x32 0x0A
\ 0x00
\ 0xB DS8 1
\ In section .text, align 4, keep-with-next
\ ?_2:
\ 0x0 0x6E 0x6F DC8 "no fail 3\012"
\ 0x20 0x66
\ 0x61 0x69
\ 0x6C 0x20
\ 0x33 0x0A
\ 0x00
\ 0xB DS8 1
\ In section .text, align 4, keep-with-next
\ ?_3:
\ 0x0 0x66 0x61 DC8 "fail 1\012"
\ 0x69 0x6C
\ 0x20 0x31
\ 0x0A 0x00
\ In section .text, align 4, keep-with-next
\ ?_4:
\ 0x0 0x66 0x61 DC8 "fail 2\012"
\ 0x69 0x6C
\ 0x20 0x32
\ 0x0A 0x00
\ In section .text, align 4, keep-with-next
\ ?_5:
\ 0x0 0x66 0x61 DC8 "fail 3\012"
\ 0x69 0x6C
\ 0x20 0x33
\ 0x0A 0x00
In the printf_no_fail
function all the printf
calls are done with a Branch with Link instruction and in the printf_fail
the first two are done the same way but the last call is done with a simple Branch and the link register is popped of the stack before the call which is only possible because the function ends right after the call.
I assume it is a problem with the linker since it seems to be related to the link register somehow and also only happens when the functions are written in a different file than main
but I can't figure out how to fix it.
Anyone got any ideas what the problem might be?
Edit:
I compile the program and generate a raw binary as additional output. I copy the binary to the boot area of the SD card and adjust the instruction jumped to to be the first instruction of main
.
At the beginning I turn on the onboard LED to see if the program is running. I have an infinite loop to see if the LED toggles which is how I know that after the printf_fail
call the program doesn't continue executing as it should. The loop counting to 10 million is to delay the output because I need time to connect the board to my serial terminal. I overwrite the __write
function and then use the printf
from the C library.
main.c
#define PH_CONF1_REG 0x0300B100
void uart0_init(void);
void printf_no_fail(void);
void printf_fail(void);
int main(void) {
uart0_init();
*((unsigned int*)PH_CONF1_REG) &= 0xF8FFFFFF;
*((unsigned int*)PH_CONF1_REG) |= 0x01000000; // turn on led
for(;;) { // inifite loop to show if led is toggling
for (volatile int i = 0; i < 10000000; i++); // delay so I can see the output
printf_no_fail();
printf_fail();
*((unsigned int*)PH_CONF1_REG) ^= 0x06000000; // toggle led
}
return 0;
}
uart.c
#define UART0_BASE 0x05000000
#define UART0_RBR (UART0_BASE + 0x0) /* receive buffer register */
#define UART0_THR (UART0_BASE + 0x0) /* transmit holding register */
#define UART0_DLL (UART0_BASE + 0x0) /* divisor latch low register */
#define UART0_DLH (UART0_BASE + 0x4) /* divisor latch high register */
#define UART0_LCR (UART0_BASE + 0xc) /* line control register */
#define UART0_LSR (UART0_BASE + 0x14) /* line status register */
#define UART0_FCR (UART0_BASE + 0x8) /* fifo control register */
#define GPIO_PH_CFG1 0x0300B100
#define GPIO_PH_PULL0 0x0300B118
#define UART_BGR 0x0300190C /* uart bus gating reset register */
void uart0_init(void) {
/* set GPIO PH9 to UART0_TX, PH10 to UART0_RX */
*((unsigned int*)GPIO_PH_CFG1) &= 0xFFFFF00F;
*((unsigned int*)GPIO_PH_CFG1) |= 0x00000550;
*((unsigned int*)GPIO_PH_PULL0) |= 0x00100000;
/* init UART clock */
*((unsigned int*)UART_BGR) |= 0x00010001;
/* select dll dlh */
*((unsigned int*)UART0_LCR) = 0x80;
/* set baudrate to 1.5M Baud*/
*((unsigned int*)UART0_DLH) = 0x0;
*((unsigned int*)UART0_DLL) = 0x1;
/* set line control : no parity, one stop bit, 8 bit data packets*/
*((unsigned int*)UART0_LCR) = 0x3;
/* enable FIFOs */
*((unsigned int*)UART0_FCR) = 0x1;
}
unsigned int __write(int handle, const unsigned char *buffer, unsigned int size) {
unsigned int n = 0;
if (handle == -1) return 0;
for ( ; size > 0; size--) {
while (!(*((unsigned int*)UART0_LSR) & (1 << 6)));
if(*buffer == '\n') {
*((unsigned int*)UART0_THR) = '\r';
while (!(*((unsigned int*)UART0_LSR) & (1 << 6)));
}
*((unsigned int*)UART0_THR) = *buffer++;
n++;
}
return n;
}