I am programming in C for a microcontroller so I am not sure if this question belongs here or on the electronics stackexchange. Just let me know if it doesn't fit here and I will move the question over there.
So I have Finite State Machine in C. This FSM is responsible for retrieving data over UART. Each state retrieves some data and performs some checks on it. Each state has three possible directions. If the received data is valid it will go to the next state. If not it will repeat its state. If the state has been repeated a certain amount of times it will move to a fail state. To indicate this direction the states return a code: ok
, err
, or repeat
. This works in all states except for the last one. Here when calling return repeat
I can see the debugger going to that statement, then going over it the the last }
of the function and then it goes to a random location in memory which I can only see in the dissassembly view of Code Composer studio. Sometimes this is 0x0 sometimes its a random memory in RAM I cant see.
What could cause such strange behavior? I have optimization options turned off in Code Composer Studio.
All states have bodies that look like this:
enum ret_codes fc_state(void) {
uint8_t buf[250];
static uint8_t fail_counter = 0;
const uint8_t max_tries = 5;
int ret = 0;
while (ret == 0) {
ret = UART_read(_uart, buf, sizeof(buf));
}
if (ret == UART_STATUS_ERROR) {
return err;
}
/*
perform tests on buf ...
*/
if (testFailed) {
fail_counter++;
if (fail_counter == max_tries) {
// Write DEN over UART ...
return err;
}
// write NACK over UART ...
return repeat; // I can see the debugger reaching this statement in the fc_state
}
// write ACK over UART ...
return ok;
} // When debugging the debugger goes here after 'return repeat' and to a random location in memory after this
// In the other states it goes back to the main loop described further below after 'return repeat'
The code for the FSM looks like this:
enum state_codes { ipv, fup, fc, fail, succ, invalid };
enum ret_codes { ok, err, repeat };
struct transition {
enum state_codes src_state; // source state
enum ret_codes ret_code; // the code it returns
enum state_codes dst_state; // the state it should transfer to if the previous mentioned code was returned
};
enum ret_codes ipv_state(void);
enum ret_codes fup_state(void);
enum ret_codes fc_state(void);
enum ret_codes fail_state(void);
enum ret_codes succ_state(void);
enum ret_codes (* states[])(void) = {ipv_state, fup_state, fc_state, fail_state, succ_state};
struct transition transition_table[] = {
{ipv, ok, fup}, // when in the ipv state and ok is returned then go to fup state
{ipv, repeat, ipv}, // This repeat works
{ipv, err, fail},
{fup, ok, fc},
{fup, repeat, fup}, // This repeats works as well
{fup, err, fail},
{fc, ok, succ},
{fc, repeat, fc}, // This repeat fails for some reason
{fc, err, fail}
};
enum state_codes lookup_transitions(enum state_codes, enum ret_codes);
The main loop for these states looks like this:
while (1) {
state_fun = states[current_state];
return_code = state_fun();
if (current_state == fail) {
return 1;
}
if (current_state == succ) {
return 0;
}
current_state = lookup_transitions(current_state, return_code);
if (current_state == invalid) {
return 1;
}
}
EDIT: I added the lookup_transitions table below. This implementation for the FSM was taken from https://mcmap.net/q/129430/-state-machines-tutorials-closed by the way. The code is written for Texas Instruments' CC2652R with an Arm Cortex-M4F CPU.
enum state_codes lookup_transitions(enum state_codes cur_state, enum ret_codes rc) {
uint8_t i;
for (i = 0; i < sizeof(transition_table); i++) {
if (cur_state == transition_table[i].src_state && rc == transition_table[i].ret_code)
return transition_table[i].dst_state;
}
return invalid; // the transition table is not configured correctly
}
This is what the Code Composer Studio disassembly viewer shows for fc_state:
505 enum ret_codes fc_state(void) {
fc_state():
000529a0: B500 push {r14}
000529a2: F1AD0D3C sub.w r13, r13, #0x3c
512 const uint8_t max_tries = 5;
000529a6: 2005 movs r0, #5
000529a8: F88D003B strb.w r0, [r13, #0x3b]
514 int ret = 0;
000529ac: 2000 movs r0, #0
000529ae: 9000 str r0, [r13]
515 while (ret == 0) {
000529b0: 9800 ldr r0, [r13]
000529b2: B948 cbnz r0, #0x529c8
516 ret = UART_read(_uart, &rheader, sizeof(rheader));
$C$L62:
000529b4: 4829 ldr r0, [pc, #0xa4]
000529b6: 6800 ldr r0, [r0]
000529b8: A901 add r1, r13, #4
000529ba: 222B movs r2, #0x2b
000529bc: F003FFC6 bl UART_read
000529c0: 9000 str r0, [r13]
515 while (ret == 0) {
000529c2: 9800 ldr r0, [r13]
000529c4: 2800 cmp r0, #0
000529c6: D0F5 beq $C$L62
518 if (ret == UART_STATUS_ERROR) {
$C$L63:
000529c8: 9800 ldr r0, [r13]
000529ca: F1B03FFF cmp.w r0, #-1
000529ce: D101 bne $C$L64
519 return err;
000529d0: 2001 movs r0, #1
000529d2: E040 b $C$L70
522 ret = 0;
$C$L64:
000529d4: 2000 movs r0, #0
000529d6: 9000 str r0, [r13]
523 while (ret == 0) {
000529d8: 9800 ldr r0, [r13]
000529da: B958 cbnz r0, #0x529f4
524 ret = UART_read(_uart, &last_fup_info, rheader.length);
$C$L65:
000529dc: 481F ldr r0, [pc, #0x7c]
000529de: F8BD202D ldrh.w r2, [r13, #0x2d]
000529e2: 6800 ldr r0, [r0]
000529e4: F10D012F add.w r1, r13, #0x2f
000529e8: F003FFB0 bl UART_read
000529ec: 9000 str r0, [r13]
523 while (ret == 0) {
000529ee: 9800 ldr r0, [r13]
000529f0: 2800 cmp r0, #0
000529f2: D0F3 beq $C$L65
526 if (ret == UART_STATUS_ERROR) {
$C$L66:
000529f4: 9800 ldr r0, [r13]
000529f6: F1B03FFF cmp.w r0, #-1
000529fa: D101 bne $C$L67
527 return err;
000529fc: 2001 movs r0, #1
000529fe: E02A b $C$L70
530 if (
$C$L67:
00052a00: 4817 ldr r0, [pc, #0x5c]
00052a02: F8DD1037 ldr.w r1, [r13, #0x37]
00052a06: 6800 ldr r0, [r0]
00052a08: 4288 cmp r0, r1
00052a0a: D115 bne $C$L68
00052a0c: 4815 ldr r0, [pc, #0x54]
00052a0e: F8DD1037 ldr.w r1, [r13, #0x37]
00052a12: 6800 ldr r0, [r0]
00052a14: 4288 cmp r0, r1
00052a16: D10F bne $C$L68
00052a18: 4813 ldr r0, [pc, #0x4c]
00052a1a: F8DD1033 ldr.w r1, [r13, #0x33]
00052a1e: 6800 ldr r0, [r0]
00052a20: 4288 cmp r0, r1
00052a22: D109 bne $C$L68
00052a24: 4811 ldr r0, [pc, #0x44]
00052a26: F8DD1033 ldr.w r1, [r13, #0x33]
00052a2a: 6800 ldr r0, [r0]
00052a2c: 4288 cmp r0, r1
00052a2e: D103 bne $C$L68
535 UPDATEMNGR_writeAck();
00052a30: F001F834 bl UPDATEMNGR_writeAck
536 return ok;
00052a34: 2000 movs r0, #0
00052a36: E00E b $C$L70
539 fail_counter++;
$C$L68:
00052a38: 490D ldr r1, [pc, #0x34]
00052a3a: 7808 ldrb r0, [r1]
00052a3c: 1C40 adds r0, r0, #1
00052a3e: 7008 strb r0, [r1]
540 if (fail_counter != max_tries) {
00052a40: 480B ldr r0, [pc, #0x2c]
00052a42: 7800 ldrb r0, [r0]
00052a44: 2805 cmp r0, #5
00052a46: D003 beq $C$L69
541 UPDATEMNGR_writeNAck();
00052a48: F001F8BC bl UPDATEMNGR_writeNAck
542 return repeat;
00052a4c: 2002 movs r0, #2
00052a4e: E002 b $C$L70
544 UPDATEMNGR_writeDen();
$C$L69:
00052a50: F001F86E bl UPDATEMNGR_writeDen
545 return err;
00052a54: 2001 movs r0, #1
546 }
$C$L70:
00052a56: B00F add r13, #0x3c
00052a58: BD00 pop {pc}
00052a5a: 46C0 mov r8, r8
return repeat
but then returns to a random location perhaps you are clobbering the stack in/* perform tests on buf ...*/
. After alluint8_t buf[250];
is a local variable. – Klongbuf
got filled beyond its limit and had overwritten the return address (in case it is kept on the stack). Please verify you never exceed the 250 bytes of thebuf
. – Complimentary$C$L70
as well as the return instruction (afaik, that should be aret
, but could be some other kind of branch theoretically). Without that, we cannot reason about what the assembly code actually does after the branch at00052a4e
. – Alexisaley