4
\$\begingroup\$

Is it possible for an out-of-control (e.g. due to stack corruption) user application to inadvertently invoke the bootloader sector code in an AVR (e.g. ATmega1284p)? Said differently, is it possible for self programming instructions in the bootloader sector to execute at any other time but on a reset?

What I'm worried about is runaway code inspiring the bootloader to corrupt the application image. If that's plausible, what if anything can be done to prevent it or at least minimize the impact of it happening.

\$\endgroup\$
4
\$\begingroup\$

It can happen yes. Imagine you somehow execute a jump instruction that happens to land in the bootloader code. It will execute quite happily - say it hit an SPM instruction.

Part of the solution to this is to have a way of detecting that whether the bootloader was entered as a result of a power on reset - information in one of the status registers of an AVR. If it wasn't then jump back to the reset vector. This works well to minimise the risk of runaway execution if the program counter keeps counting through everything in the flash and gets to the bootloader code - the PORF flag won't be set, so it will get sent back to the reset vector.

However this does not protect from inadvertently jumping in to part of the bootloader routines with a branch or IJMP type instruction. In this case what are the options to stop what would ensue.

Fortunately the SPM instruction requires a timed sequence of register writes (write to SPMCSR then within 4 cycles call SPM) to get it to execute, so if you hit just an SPM instruction it wouldn't do anything - your run away would have to run through the full programming sequence. But this just means there is slightly less chance of an accident - though it doesn't prevent it.

One option you could have in your bootloader is your own check during the timed sequence to see if the reset flags are set. The bootloader is the first thing that runs, and it can clear these reset source flags once it exits. That way if you ever accidentally jump into the SPM sequence it could not complete the sequence as it was executed from something that wasn't the bootloader - because the reset flags would have been cleared by the bootloader already.

\$\endgroup\$
  • \$\begingroup\$ "Part of the solution to this is to have a way of detecting that whether the bootloader was entered as a result of a power on reset" - if we jumped into the bootloader because of a corrupted program counter, this is useless, because the chance that the random jump takes us to the beginning of the bootloader instead of the middle of it is very small. \$\endgroup\$ – vsz May 28 '15 at 8:13
  • \$\begingroup\$ @vsz hence the line below - "however this does not protect from inadvertently jumping into the bootloader routines" \$\endgroup\$ – Tom Carpenter May 28 '15 at 9:06
  • \$\begingroup\$ Wouldn't interjecting code into the timed SPM sequence to check for reset flags being set disrupt the timed sequence? \$\endgroup\$ – vicatcu May 29 '15 at 4:04
  • \$\begingroup\$ @vicatcu: not if the jump lands us in front of the timed sequence. The timed sequence is only useful to not carry out a corrupted instruction which looks like an SPM. If we jumped into the bootloader by mistake, then there is nothing we can do. This is why I protect the controller from a random jump by assuring a safe brownout level, and by having the Boot Reset flag always active, so the boot loader is the first to start and it jumps immediately to the main code if very specific conditions in the eeprom are not met. This helps in case a failed update corrupted the main code. \$\endgroup\$ – vsz May 29 '15 at 5:26
3
\$\begingroup\$

This comes down to: Is the Bootloader section on the AVR reachable from main code, regardless the chances?

As such: Is there a jump location that could be pushed onto the stack (or written there by RAM writes) that could lead back to the Bootloader?

The answer, unfortunately, is yes. If your application is vulnerably written in the sense of bad RAM use or bad pushing, then this is a risk. As can be read in section 26.6 of your specific device's datasheet (p273 in this version: ATMega1284p Datasheet Online):

Entering the Boot Loader takes place by a jump or call from the application program.

((EDIT: It also notes you can reset into the bootloader, but that's not to the point of my answer, but since Tom's answer, posted while I was searching for the right page in this datasheet, refers to it, I want to avoid confusion. I am not contradicting that, I'm just clipping the section.))

So the code is by definition reachable by Program Counter interference and as such can also be "popped off the stack by accident", no clear provisions made in this device. Most devices of this type and cost-range work like that.

Some more advanced chips do have special fully secluded boot sections, that can only be reset into, but AVRs don't have that. They only have write protection lock bits that you can use to protect at least the bootloader from being overwritten by the bootloader and as such make any error at least recoverable (if the reset vector is the bootloader-start)

However... yup, however time:

If you use a decent tool for developing the application and bootloader code (Atmel Studio is a pretty good start, free and quite okay) and make sure you leave some room for a couple of subroutine calls, which should be easy on a device with that much memory, the risk becomes microscopic. The tool will always tell you how much of what you are using. In bytes used and percentages and often configurably also bytes available. Firmware development is still up to people who know what they are doing, so if you know you use a bunch of calls and you see "Data: 16316 bytes, 99.6% used" (knowing it has 16kbytes) you should think to yourself: Ooof, only 68 bytes of safe stack, that'll be trouble.

There are tools that try to figure out your deepest push (in and around calls and interrupts) and determine a stack size, but to my experience they are either well paid tools, highly unreliable, slow or all three. There might be easy plugins that let you define "I want a 512 byte stack, because that'll be safe no matter what" and use a #warning or #error when your RAM left is below that. In fact, that should be doable with some compiler scripting as well. (I am too lazy to research the directives that need to be sent and got for that at 2:30AM, but I have a suspicion they might well exist)

With regard to bad pushes: Either write C/C++ (-like languages) and let the compilers do the pushing and popping on their own, or develop a tactic if you really do need to use Assembler to make sure every push has a pop.

Back in the day, before the AVR - and such for me, before Atmel - and on the first AT90S types (sort of Tinies, I suppose) I'd just write down my pushes in order, and when they got popped (at once or not) I'd cross them off. Keeping a sort of paper stack-accounting. Some project folders (the physical types, with A4 sheets in them) still contain 5 or 6 -yellow/brown- pages of per-function crossed off pushes. Partly keep-sake, partly in case I wanted to check back whether I hadn't forgotten to do that for function X or Y.

Note on RAM for stack:

There is of course no general rule to be made for stack size, but I usually try to reserve, when I start from flowcharts or the like: (X bytes + {Program Counter Size}) * ({max subroutine call depth} + 1) + 1% of total RAM

The + 1 behind the call depth is for the interrupt that can be called at any point during execution. X is the number of registers the compiler would find volatile enough to push, which depends on your program a little, but often it's between 3 and 8. I usually start with 6 and then when it starts getting risky/important check the generated subroutines with "PUSH" highlighted. The +1% is a safety measure, if you let the compiler build the assembler, it will not use the stack that much, because it doesn't do the dirty stack tricks that humans sometimes do, so 1% will be royal on all but the tiniest of AVRs (pun slightly intended).

If you run out of RAM because of that estimation, you're either at the end of the development cycle and can actually research the total stack use without it being a daily exercise, or you're using the wrong controller ("too small") or using the RAM too liberally.

This last rule is almost universal, even if on a Tiny after only a day your rule blocks you with 100bytes of 128bytes available: Very likely way too deep a subroutine stack for that little a processor. But, exceptions will always exist.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.