Shrink vector table

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just noticed the discussion from earlier this year about patching gcc to shrink the interupt jump table. - http://avr.2057.n7.nabble.com/Patch-avr-Shrink-interrupt-vector-table-down-to-last-used-entry-td19487.html

My analysis of the discussion is that they decided that a complex project build process is not very complex, but that understanding the machine level behaviour of your process is complex: that rebuilding your library is not complex, but hardware interrupts are complex.

That is an inversion of the actual situation here. Most code here is written in assembler: one of the effects of that is that knowledge of the internals of building C library start code is close to zero.

Nobody here would have any trouble identifing which interrupts are used, or debugging code with stray interrupt errors. On the other hand, the suggested alternatives ("Just -nostartfiles, add gcrt1.S to the project and define _VECTORS_SIZE") are complete unknowns.

To my sorrow, they seem to have decided not to go ahead with the suggestion.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

On the other hand, the suggested alternatives ("Just -nostartfiles, add gcrt1.S to the project and define _VECTORS_SIZE") are complete unknowns.

Then time to learn. When you build:

int main(void) {
  while(1);
}

for an AVR with a small vector table like a tiny13 you will see this in the .lss file:

Disassembly of section .text:

00000000 <__vectors>:
   0:	09 c0       	rjmp	.+18     	; 0x14 <__ctors_end>
   2:	0e c0       	rjmp	.+28     	; 0x20 <__bad_interrupt>
   4:	0d c0       	rjmp	.+26     	; 0x20 <__bad_interrupt>
   6:	0c c0       	rjmp	.+24     	; 0x20 <__bad_interrupt>
   8:	0b c0       	rjmp	.+22     	; 0x20 <__bad_interrupt>
   a:	0a c0       	rjmp	.+20     	; 0x20 <__bad_interrupt>
   c:	09 c0       	rjmp	.+18     	; 0x20 <__bad_interrupt>
   e:	08 c0       	rjmp	.+16     	; 0x20 <__bad_interrupt>
  10:	07 c0       	rjmp	.+14     	; 0x20 <__bad_interrupt>
  12:	06 c0       	rjmp	.+12     	; 0x20 <__bad_interrupt>

00000014 <__ctors_end>:
  14:	11 24       	eor	r1, r1
  16:	1f be       	out	0x3f, r1	; 63
  18:	cf e9       	ldi	r28, 0x9F	; 159
  1a:	cd bf       	out	0x3d, r28	; 61
  1c:	02 d0       	rcall	.+4      	; 0x22 
1e: 02 c0 rjmp .+4 ; 0x24 <_exit> 00000020 <__bad_interrupt>: 20: ef cf rjmp .-34 ; 0x0 <__vectors> 00000022
: #include int main(void) { 22: ff cf rjmp .-2 ; 0x22
00000024 <_exit>: 24: f8 94 cli 00000026 <__stop_program>: 26: ff cf rjmp .-2 ; 0x26 <__stop_program>

main() itself is simply the:

00000022 
: 22: ff cf rjmp .-2 ; 0x22

Everything else there is the C Run Time (CRT). It provides the RJMP at 0 that lands at location 14:. The code there then clears R1 as the C compiler always relies on R1 holding 0 (to save it having to clear a register every time it needs a 0 value). Having cleared it the value is written to SREG. This is mainly to ensure that I is clear as control may have got to 0 by something other than a poweron reset and I could be enabled. It sthen sets the stack. Because this is a very small AVR it only has SPL not SPH so only one LDI and one OUT is needed to set the stack. Finally it RCALL's main(). If main() makes the mistake of not being infinite but returns then it will get back to 1e: so this then has code to jump to _exit() and _exit() itself is nothing more than a CLI and an infinite __stop_program loop with a single RJMP.

And that would be all the CRT had to do if it weren't for the fact that AVRs support interrupts. As the silicon defines a fixed number of vectors after the reset jump the CRT code also provides a table of jumps to locate there but it initially sets each entry with a "weak link" to a routine called __bad_interrupt() and that routine, in turn has a jump to location 0. (rjmp .-34 in this case in fact).

When you build for tiny13 you pass -mmcu=attiny13 to both the compiler and the linker. For the compiler it tells it stuff like the fact that it is a Tiny so MUL cannot be generated and for the linker, amongst other things it tells it that as well as the main() code you provide from the main.o you build (basically that single RJMP) it should link all this together with a file called crttn13.o. It has a mapping like "attiny13" to "crttn13.o" for every AVR you might build for.

In your avr-gcc installation these crt*.o files are in ..\lib\*. For example:

E:\WinAVR-20100110\avr\lib\avr25>dir crttn13.o
 Volume in drive E is VBOX_windows
 Volume Serial Number is 0000-0801

 Directory of E:\WinAVR-20100110\avr\lib\avr25

06/01/2010  20:10             1,528 crttn13.o

(I'm using WinAVR not Atmel Toolchain as the pathnames are more sensible!). That file is in the lib\avr25 directory because tiny13 is a member of the "avr25" (2.5) architecture family. You can see what it contains like this:

E:\WinAVR-20100110\avr\lib\avr25>avr-objdump -S crttn13.o

crttn13.o:     file format elf32-avr


Disassembly of section .text:

00000000 <__bad_interrupt>:
   0:   00 c0           rjmp    .+0             ; 0x2 <__bad_interrupt+0x2>

Disassembly of section .vectors:

00000000 <__vectors>:
   0:   00 c0           rjmp    .+0             ; 0x2 <__vectors+0x2>
   2:   00 c0           rjmp    .+0             ; 0x4 <__vectors+0x4>
   4:   00 c0           rjmp    .+0             ; 0x6 <__vectors+0x6>
   6:   00 c0           rjmp    .+0             ; 0x8 <__vectors+0x8>
   8:   00 c0           rjmp    .+0             ; 0xa <__vectors+0xa>
   a:   00 c0           rjmp    .+0             ; 0xc <__vectors+0xc>
   c:   00 c0           rjmp    .+0             ; 0xe <__vectors+0xe>
   e:   00 c0           rjmp    .+0             ; 0x10 <__vectors+0x10>
  10:   00 c0           rjmp    .+0             ; 0x12 <__vectors+0x12>
  12:   00 c0           rjmp    .+0             ; 0x14 <__vectors+0x14>

Disassembly of section .init2:

00000000 <.init2>:
   0:   11 24           eor     r1, r1
   2:   1f be           out     0x3f, r1        ; 63
   4:   c0 e0           ldi     r28, 0x00       ; 0
   6:   cd bf           out     0x3d, r28       ; 61

Disassembly of section .init9:

00000000 <.init9>:
   0:   00 d0           rcall   .+0             ; 0x2 <.init9+0x2>
   2:   00 c0           rjmp    .+0             ; 0x4 <__bad_interrupt+0x4>

Notice that it provides code for sections called ".vectors", ".init2" and ".init9". You can read about the sections here:

http://www.nongnu.org/avr-libc/u...

but basically it's about getting the right things into the right place in memory. A linker script gives a recipe for how things should be laid out in memory (flash and RAM) for flash it says anything from .vectors should go at the very start then for the overall ".text" it says that first there should be any codes from .init0 to .init9 then the user provided contents of .text then anything from .fini0 to .fini9.

Code in .initN sections are not callable functions but are simply "fall through" code that is placed "inline". So the code in .init2 is just placed after anything in .init1 and before anything in .init3 in memory and all this immediately after .vectors (and the thing at the very start of .vectors is always a jump to the end of .vectors so it bumps into the .initN code).

Anyway all crt*.o files (191 in WinAVR, 303 in Atmel Toolchain as it now supports many more AVRs) are actually generated from a single source file. You can see it (well actually the latest version) here:

http://svn.savannah.nongnu.org/v...

Now this is where your Asm knowledge may be useful (but only if you speak "as" dialect). Note the macro definition:

	.macro	vector name
	.if (. - __vectors < _VECTORS_SIZE)
	.weak	\name
	.set	\name, __bad_interrupt
	XJMP	\name
	.endif
	.endm

In particular note the .if test in that. This macro only actually outputs some code (some kind of jump called "XJMP") if the difference between the current location (".") and the start of the vector table ("__vectors") is less than VECTORS_SIZE. Well this code is built (191 or 303 times) for each different AVR and when it is it will use a different io*.h file each time. If you look at iotn13.h then after the definition of all the *_vect names it has:

#define _VECTORS_SIZE 20

So when the Asm code of gcrt1.S is assembled for the tiny13 and VECTORS_SIZE is defined as 20 (so reset jump and 9 vectors) then when the code hits..

	.global	__vectors
	.func	__vectors
__vectors:
	XJMP	__init
	vector	__vector_1
	vector	__vector_2
	vector	__vector_3
	vector	__vector_4
	vector	__vector_5
	vector	__vector_6
	vector	__vector_7
	vector	__vector_8
	vector	__vector_9
	vector	__vector_10
	vector	__vector_11
	vector	__vector_12
	vector	__vector_13
	vector	__vector_14
	vector	__vector_15
..
	vector	__vector_126
	vector	__vector_127
	.endfunc

Only the first 9 invocations of the macro will generate an XJMP (which for tiny13 translates to RJMP) and the other 100+ invocations will do nothing.

If your C program only actually used __vector_3 (aka TIM0_OVF_vect) then if you could persuade it to see VECTORS_SIZE == 8 rather than VECTORS_SIZE == 20 from the header then you would build a smaller vector table.

You don't need to touch anything else in gcrt1.S as you want everything else as before (I assume) but just intercept the definition of VECTORS_SIZE. I'd probably do that simply by taking a copy, editing it and adding something like

...
#undef VECTORS_SIZE
#define VECTORS_SIZE 8

You add gcrt1.S to the list of files you build in your project and you pass -nostartfiles to the linker which says "although I'm telling you to link for attiny13 and that would mean you normally link in the contents of crttn13.o I don't want it on this occassion". Thus you manage to replace the default CRT with your own edited copy.

And that's pretty much it (I think!).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

melbourne wrote:

Nobody here would have any trouble identifying which interrupts are used, or debugging code with stray interrupt errors.

The last part of your sentence is an assumption. Based on our many years of embedded systems experience, debugging code with stray interrupt errors is not classified as a beginner task. Mainly because the symptoms will vary from application to application, perhaps only with subtle effects that are very hard to trace. Hence the reason to keep the full table with "bad_interrupt" vectors. It helps to capture a whole class of errors and keep the device (and your application) from doing stupid things.

Remember, in Engineering and Embedded Systems, one is ALWAYS dealing with TRADEOFFS. The pros of gracefully dealing with a class of errors that are hard to diagnose outweigh the cons of making it easy for developers to use only slightly more flash, and even then it's using the flash in a way not really intended by the manufacturer. There are extremely few cases of real applications that need to have those few extra bytes of flash.

Now, to rehash the arguments, we are NOT setting up a roadblock to developers to using that extra flash. On the contrary, it can still be done. We've shown you how to do it. All we are doing is not going through the effort to make it EASY for the developer. It shouldn't be easy. It should require expert knowledge of the very real tradeoffs that you have to do, in order to gain those few extra bytes of flash. An expert will also know the tradeoffs of dealing with a class of errors that are hard to track vs. the economics of moving to a compatible chip with a larger flash size to accommodate a future expansion of the software (for new features/bug fixes).

There are a lot of things to consider before just wishing for an easy way to break good judgement in designing software for an embedded system.

melbourne wrote:

On the other hand, the suggested alternatives ("Just -nostartfiles, add gcrt1.S to the project and define _VECTORS_SIZE") are complete unknowns.

This just shows that you're not an expert user of the toolchain. An expert user can take a look at that sentence and understand pretty much what needs to be done, with maybe a quick lookup on how and where _VECTORS_SIZE should be defined.

melbourne wrote:

To my sorrow, they seem to have decided not to go ahead with the suggestion.

As was said, it can be done, and you now know how to do it. Nothing is stopping you.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The first reply, from Clawson, is actually very good. I would like to see that as a sticky: I've spent days on the web identifying material like that.

The second reply, from EW, seems to include some misunderstandings of what I was trying to say.

"Nobody here would have any trouble identifing which interrupts are used, or debugging code with stray interrupt errors."

EW seems to have assumed that I was not telling the truth, and has given us the gratutious advice that "debugging code with stray interrupt errors is not classified as a beginner task"

Thanks EW. Your assumption that I wasn't telling the truth is perhaps due to a my use of the word "here", but it demonstrates the attitude I was decrying.

EW did get some things correct though: nobody "here", where I work, as part of a mixed team of developers with 2-30 years embedded experience, nobody here is an expert user of the avr gcc tool chain.

It is unfortunate that basic embedded programming like shrinking the jump table should require "expert" knowledge when using avr gcc: that characteristic remains one of the reasons why avr gcc is not used for the majority of the work here. And EW has kindly verified what I wrote about the basis on which that decision was made.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

melbourne wrote:

It is unfortunate that basic embedded programming like shrinking the jump table should require "expert" knowledge when using avr gcc: that characteristic remains one of the reasons why avr gcc is not used for the majority of the work here. And EW has kindly verified what I wrote about the basis on which that decision was made.

The expert knowledge needed has nothing to do with the toolchain. With a little digging in the user manual, or here as you discovered, you can figure out how to do it. No it's not a simple, convenient switch.

The expert knowledge needed is in figuring out what you're going to do if there is a stray interrupt that jumps to the middle of some code. Keeping the interrupt table intact, with default vectors for bad interrupts, helps you in the end.

Reducing the IVT just for a little extra code space is rarely a good tradeoff. You're trading a tiny amount of code for a lot of extra systemic risk.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree with Eric, I thin kyou are being a little unfair to GCC. If you don't want a vector table (or to take over an make your own implementation) it is as simple as "-nostartfiles". Simple as that.

If, on the other hand you want to keep some but not all of what the existing CRT brings then, yes, it is a bit of a job to achieve that but nothing says you have to do that. It's simply that the existing CRT implementation is so good it's very tempting to try and re-use large sections of it - but it wasn't designed to be broken apart: to have done that might have imposed constraints on 99.9999% of the users simply to satisfy the 0.0001% who want something slightly different.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does it really matter how big the jump table is?

IMHO, if you're that short of flash, you've chosen the wrong device.

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
"Nobody here would have any trouble [...] debugging code with stray interrupt errors."

FWIW, we've seen many cases here where people have trouble debugging code with an interrupt enabled but with no ISR "defined, declared and vetored" for it. The have come here perplexed as to why the app behaves in the strangest of ways.

Whether such a situation counts as "code with stray interrupt errors" or not, I believe it is a hint about the "debugging code with stray interrupt errors" will be something that many would have trouble with.

A seasoned embedded systems and AVR programmer would likely have little to no trouble with this. I would expect that developer to be able to search out documentation on how to tweak the build process and persuade the toolchain to do the non-standard stuff.

You also need to realize that most developers of the GCC tool chain has a C perspective on things rather than an assembler perspective. And when they have an assembler perspective they still have it with their C glasses on.

EDIT: Sometimes one shoud read a thread twice before posting to it. I read the first "here" as "here at ABRfreaks". I missed the clarification in the rebuttal to Eric:

Quote:
"here", where I work

My answer could have been more dense then: Yes, the GCC folks have a C perspective, rather than a "naked assembler" one. It will very likely not change.

If this is the cause of GCC not being used much at your place of work then you will most likely be happiest with leaving it at that. Any other stance will probably just make you frustrated.

If you can not accept an assembler not "heavily stained" by it being a part of a multi-platform multi-language multi-target tool chain then the GCC tool chain is simply not for you.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]