Jump to content
Sign in to follow this  
jayaura

Analyzing blinky's disassembly

Recommended Posts

Hello everyone,

 

I disassembled the following code:

 

#include <msp430.h>


#define RED BIT0
#define GREEN BIT6


int main(int argc, const char *argv[])
{
        WDTCTL = WDTPW + WDTHOLD;
        P1DIR = BIT6 | BIT0;
        while (1) {
                P1OUT = BIT6;
                __delay_cycles(500000);
                P1OUT ^= (BIT6 | BIT0);
                __delay_cycles(500000);
        }
        return 0;
}

On running msp430-objdump -D hello_world.elf, I got:

Disassembly of section .text:

0000c000 <__watchdog_support>:
    c000:	55 42 20 01 	mov.b	&0x0120,r5	//0x120 is WDTCTL
    c004:	35 d0 08 5a 	bis	#23048,	r5	;#0x5a08 //Supposed to be WDTPW + WDTHOLD
    c008:	82 45 00 02 	mov	r5,	&0x0200	 // copying to RAM

0000c00c <__init_stack>:
    c00c:	31 40 00 04 	mov	#1024,	r1	;#0x0400

0000c010 <__do_copy_data>:
    c010:	3f 40 00 00 	mov	#0,	r15	;#0x0000
    c014:	0f 93       	tst	r15		
    c016:	08 24       	jz	$+18     	;abs 0xc028 
    c018:	92 42 00 02 	mov	&0x0200,&0x0120	
    c01c:	20 01 
    c01e:	2f 83       	decd	r15		
    c020:	9f 4f 88 c0 	mov	-16248(r15),512(r15);0xc088(r15), 0x0200(r15)
    c024:	00 02 
    c026:	f8 23       	jnz	$-14     	;abs 0xc018

0000c028 <__do_clear_bss>:
    c028:	3f 40 00 00 	mov	#0,	r15	;#0x0000
    c02c:	0f 93       	tst	r15		
    c02e:	07 24       	jz	$+16     	;abs 0xc03e
    c030:	92 42 00 02 	mov	&0x0200,&0x0120	
    c034:	20 01 
    c036:	1f 83       	dec	r15		
    c038:	cf 43 00 02 	mov.b	#0,	512(r15);r3 As==00, 0x0200(r15)
    c03c:	f9 23       	jnz	$-12     	;abs 0xc030

0000c03e <main>:
    c03e:	b2 40 80 5a 	mov	#23168,	&0x0120	;#0x5a80
    c042:	20 01 
    c044:	f2 40 41 00 	mov.b	#65,	&0x0022	;#0x0041
    c048:	22 00 
    c04a:	f2 40 40 00 	mov.b	#64,	&0x0021	;#0x0040
    c04e:	21 00 
    c050:	3e 40 03 00 	mov	#3,	r14	;#0x0003
    c054:	3f 40 06 8b 	mov	#-29946,r15	;#0x8b06
    c058:	1f 83       	dec	r15		
    c05a:	fe 23       	jnz	$-2      	;abs 0xc058
    c05c:	1e 83       	dec	r14		
    c05e:	fc 23       	jnz	$-6      	;abs 0xc058
    c060:	03 43       	nop			
    c062:	f2 e0 41 00 	xor.b	#65,	&0x0021	;#0x0041
    c066:	21 00 
    c068:	3e 40 03 00 	mov	#3,	r14	;#0x0003
    c06c:	3f 40 06 8b 	mov	#-29946,r15	;#0x8b06
    c070:	1f 83       	dec	r15		
    c072:	fe 23       	jnz	$-2      	;abs 0xc070
    c074:	1e 83       	dec	r14		
    c076:	fc 23       	jnz	$-6      	;abs 0xc070
    c078:	03 43       	nop			
    c07a:	e7 3f       	jmp	$-48     	;abs 0xc04a

0000c07c <__stop_progExec__>:
    c07c:	32 d0 f0 00 	bis	#240,	r2	;#0x00f0
    c080:	fd 3f       	jmp	$-4      	;abs 0xc07c

0000c082 <__ctors_end>:
    c082:	30 40 86 c0 	br	#0xc086	

0000c086 <_unexpected_>:
    c086:	00 13       	reti			

Disassembly of section .noinit:

00000200 <__wdt_clear_value>:
	...

Disassembly of section .vectors:

0000ffe0 <__ivtbl_16>:
    ffe0:	82 c0 82 c0 	bic	r0,	&0xc082	
    ffe4:	82 c0 82 c0 	bic	r0,	&0xc082	
    ffe8:	82 c0 82 c0 	bic	r0,	&0xc082	
    ffec:	82 c0 82 c0 	bic	r0,	&0xc082	
    fff0:	82 c0 82 c0 	bic	r0,	&0xc082	
    fff4:	82 c0 82 c0 	bic	r0,	&0xc082	
    fff8:	82 c0 82 c0 	bic	r0,	&0xc082	
    fffc:	82 c0 00 c0 	bic	r0,	&0xc000

My questions are:

 

1) In <__watchdog_support>

c000:    55 42 20 01     mov.b    &0x0120,r5    //0x120 is WDTCTL
c004:    35 d0 08 5a     bis    #23048,    r5    ;#0x5a08 //Supposed to be WDTPW + WDTHOLD
c008:    82 45 00 02     mov    r5,    &0x0200     // copying to RAM

The second line - WDTHOLD is the 8th bit in WDTCTL. So shouldnt it be 0x5a80 ? Why is it 0x5a08 ?

Why copy the modified content to RAM ? Why not directly write to WDTCTL register?

 

But later in main:

c03e:    b2 40 80 5a     mov    #23168,    &0x0120    ;#0x5a80

Now this seems consistent. So why is that "watchdog support" section even there?

 

 

2) In section <__do_copy_data>

   

c010:    3f 40 00 00     mov    #0,    r15    ;#0x0000
c014:    0f 93     tst    r15        
c016:    08 24     jz    $+18     ;abs 0xc028 

When #0 is explicitly moved to r15, after tst, its pretty obvious that jz will be evaluate to true. Then why not unconditionally jump to 0xc028 ?

 

If thats the case, what about the code just below jz? that is *dead* code, right?

Share this post


Link to post
Share on other sites

All your questions relate to mspgcc's runtime support infrastructure and are independent of the program you've compiled.

 

My questions are:

 

1) In <__watchdog_support>

c000:    55 42 20 01     mov.b    &0x0120,r5    //0x120 is WDTCTL
c004:    35 d0 08 5a     bis    #23048,    r5    ;#0x5a08 //Supposed to be WDTPW + WDTHOLD
c008:    82 45 00 02     mov    r5,    &0x0200     // copying to RAM
The second line - WDTHOLD is the 8th bit in WDTCTL. So shouldnt it be 0x5a80 ? Why is it 0x5a08 ?

Why copy the modified content to RAM ? Why not directly write to WDTCTL register?

 

Because the value that needs to get written to the watchdog timer to reset it varies depending on MCU, and the expression computed by __watchdog_support reads the current setting then changes it to be a valid reset that preserves that setting. (Specifically, in some MCU families the bits that produce the power-up default timeout interval are different because of clock differences, so having a constant won't work across all platforms. I didn't want yet another chip-specific object file, so I found a general solution.)

 

It's copied into RAM so that subsequent uses of the intrinsic __watchdog_clear() know what value to write. mspgcc also supports intrinsics __set_watchdog_clear_value() and __get_watchdog_clear_value(). It is the only toolchain that does this, so since it's not supported anymore people will have to create a new solution to the problem.

But later in main:

c03e:    b2 40 80 5a     mov    #23168,    &0x0120    ;#0x5a80
Now this seems consistent. So why is that "watchdog support" section even there?

 

Because your example doesn't use the built-in watchdog support, but the runtime system doesn't know that so it leaves it present.

 

2) In section <__do_copy_data>

 

c010:    3f 40 00 00     mov    #0,    r15    ;#0x0000
c014:    0f 93     tst    r15        
c016:    08 24     jz    $+18     ;abs 0xc028 
When #0 is explicitly moved to r15, after tst, its pretty obvious that jz will be evaluate to true. Then why not unconditionally jump to 0xc028 ?

 

If thats the case, what about the code just below jz? that is *dead* code, right?

 

The value copied to r15 is defined at link time, and is not zero when you have global variables that must be initialized before main() gets invoked.

Share this post


Link to post
Share on other sites

BTW: The value in __watchdog_support() is not supposed to be WDTHOLD. If you choose to turn off the watchdog in main() (as your program does), that's fine, but the toolchain should not make that policy decision for you. Instead it preserves the power-up setting, and uses the value it saved on startup to ensure the watchdog does not fire during the startup code.

Share this post


Link to post
Share on other sites

In a nutshell:

 

Programming in C:  Now why did that stupid compiler do that in a wasteful and ultra-careful way?

 

Programming in Assembly: Now why didn't that stupid programmer spend 4 extra cycles to make that routine generally safer?

 

-----

 

Once upon a time, there were people who coded PC applications in assembly because it made for faster programs.  Then the machines got faster, compliers got a little better, and assembly really wasn't worth it any more on PC's.

 

HOWEVER:

 

Because libraries have to work (generally) on multiple architectures, and programs (generally) have to co-exist in memory and systems (generally) are multi-tasking, and interrupt routines (generally) must leave the environment exactly as they found them, there are things that the compliers are going to do, just to be 'good citizens'.  A good part of this 'good citizenship' can be dropped if you have intimate knowledge about the environment that the code is going to have to run in, which we (generally) have.

 

And when the amount of application logic is large compared to the amount of hardware specific logic, these tradeoffs make a lot of sense.

 

BUT:

 

On our little '430s the hardware specific logic is probably 80% of our entire codebase, so the generalizations can begin to consume a non-trivial amount of space and cycles, which, as you know, we never have quite enough of....

 

Viva la Assemblia!  - lol

Share this post


Link to post
Share on other sites

All your questions relate to mspgcc's runtime support infrastructure and are independent of the program you've compiled.

 

Because the value that needs to get written to the watchdog timer to reset it varies depending on MCU, and the expression computed by __watchdog_support reads the current setting then changes it to be a valid reset that preserves that setting. (Specifically, in some MCU families the bits that produce the power-up default timeout interval are different because of clock differences, so having a constant won't work across all platforms. I didn't want yet another chip-specific object file, so I found a general solution.)

 

It's copied into RAM so that subsequent uses of the intrinsic __watchdog_clear() know what value to write. mspgcc also supports intrinsics __set_watchdog_clear_value() and __get_watchdog_clear_value(). It is the only toolchain that does this, so since it's not supported anymore people will have to create a new solution to the problem.

 

Because your example doesn't use the built-in watchdog support, but the runtime system doesn't know that so it leaves it present.

 

 

The value copied to r15 is defined at link time, and is not zero when you have global variables that must be initialized before main() gets invoked.

 

Dear pabigot

 

Thank you for that informative response. 

 

I tried adding __set_watchdog_clear_value(WDTPW+WDTHOLD); ed at the start of main() and checked the disassembled output, but I get the same watchdog_support section as before. What is the right way to use the inbuilt watchdog ?

 

It might be an overkill for me right now to use these advanced stuff, but I would also like to know about all the other internal features that mspgcc has. Is there a documentation about them anywhere? I googled, but couldnt find anything regarding the watchdog functions you mentioned. The best I could find was http://mspgcc.sourceforge.net/manual/book1.html

 

How did you compile it? What optimization options did you use? Try -Os if you haven't already.

 

Sent from my Galaxy Note II with Tapatalk 4

 

I already had -Os in the build.

Share this post


Link to post
Share on other sites

When using 'C', the compiler always gives you enough rope to hang yourself. When I was writing the msp430 fabooh framework, I also wanted to get rid of this extra bloat. When I compile this:

https://github.com/RickKimball/fabooh/blob/master/examples/basic/blink/blink.cpp

the output is this:

msp430g2231in14_release/blink.elf:     file format elf32-msp430


Disassembly of section .text:

0000f800 <__watchdog_support>:
    f800:	b2 40 80 5a 	mov	#23168,	&0x0120	;#0x5a80
    f804:	20 01 

0000f806 <__init_stack>:
    f806:	31 40 80 02 	mov	#640,	r1	;#0x0280

0000f80a <__do_clear_bss>:
      BCSCTL1 = CALBC1_8MHZ;
      DCOCTL = CALDCO_8MHZ;
#endif
    }
    else if (MCLK_FREQ == 1000000) {
      BCSCTL1 = CALBC1_1MHZ;
    f80a:	d2 42 ff 10 	mov.b	&0x10ff,&0x0057	
    f80e:	57 00 
      DCOCTL = CALDCO_1MHZ;
    f810:	d2 42 fe 10 	mov.b	&0x10fe,&0x0056	
    f814:	56 00 
        clear(pins_mask);
        pren |= pins_mask;
    }

    static ALWAYS_INLINE void setmode_output(const uint8_t pins_mask) {
        pdir |= pins_mask;
    f816:	d2 d3 22 00 	bis.b	#1,	&0x0022	;r3 As==01
    static ALWAYS_INLINE void clear(const uint8_t pins_mask) {
        pout &= ~pins_mask;
    }

    static ALWAYS_INLINE void toggle(const uint8_t pins_mask) {
        pout ^= pins_mask;
    f81a:	d2 e3 21 00 	xor.b	#1,	&0x0021	;r3 As==01

void loop(void) {

    while (1) {
        RED_LED::toggle();
        delay(msec_delay);
    f81e:	2e 43       	mov	#2,	r14	;r3 As==10
    f820:	3f 40 82 45 	mov	#17794,	r15	;#0x4582
    f824:	1f 83       	dec	r15		
    f826:	fe 23       	jnz	$-2      	;abs 0xf824
    f828:	1e 83       	dec	r14		
    f82a:	fc 23       	jnz	$-6      	;abs 0xf824
    f82c:	03 43       	nop			
    f82e:	f5 3f       	jmp	$-20     	;abs 0xf81a

0000f830 <__stop_progExec__>:
    f830:	32 d0 f0 00 	bis	#240,	r2	;#0x00f0
    f834:	fd 3f       	jmp	$-4      	;abs 0xf830

0000f836 <__ctors_end>:
    f836:	30 40 3a f8 	br	#0xf83a	

0000f83a <_unexpected_>:
    f83a:	00 13       	reti			

Disassembly of section .vectors:

0000ffe0 <__ivtbl_16>:
    ffe0:	36 f8 36 f8 36 f8 36 f8 36 f8 36 f8 36 f8 36 f8     6.6.6.6.6.6.6.6.
    fff0:	36 f8 36 f8 36 f8 36 f8 36 f8 36 f8 36 f8 00 f8     6.6.6.6.6.6.6...
I compiled the code above like this:

$ make
Compiling file: blink.cpp
msp430-g++ -c -mmcu=msp430g2231 -Os -Wall -Wextra -DFIXMATH_NO_CACHE -DFIXMATH_NO_64BIT -DFIXMATH_NO_ROUNDING -fwrapv -fmessage-length=0 -ffunction-sections -fdata-sections -std=gnu++98 -g -ggdb3 -fno-rtti -fno-exceptions -fverbose-asm -DFABOOH -DF_CPU=1000000 -MD -MP -MF msp430g2231in14_release/blink.d -I../../../  -I../../../board/msp430g2231in14  -I../../../include/msp430/core  -I../../../include/3rdparty blink.cpp -o msp430g2231in14_release/blink.o
 
Linking target: msp430g2231in14_release/blink.elf
msp430-gcc -mmcu=msp430g2231 -DFIXMATH_NO_CACHE -DFIXMATH_NO_64BIT -DFIXMATH_NO_ROUNDING -fwrapv -fmessage-length=0 -ffunction-sections -fdata-sections -mmcu=msp430g2231 -mdisable-watchdog -Wl,--gc-sections,-Map=msp430g2231in14_release/blink.map,-umain  -L../../../board/msp430g2231in14   msp430g2231in14_release/blink.o   -o msp430g2231in14_release/blink.elf
If you look at the main.h header file, you can see how I replaced the system functions with my own versions that do nothing:

 

https://github.com/RickKimball/fabooh/blob/master/include/msp430/core/main.h#L116

 

Another thing to reducing the size is to use the -mdisable-watchdog flag. That gets rid of all extra WDT code.

 

Of course when all this fails to work, you can at least claim your code is small :)

 

-rick

Share this post


Link to post
Share on other sites

I tried adding __set_watchdog_clear_value(

WDTPW+WDTHOLD); ed at the start of main() and checked the disassembled output, but I get the same watchdog_support section as before. What is the right way to use the inbuilt watchdog ?

Nothing short of editing the linker script or providing an alternative definition will affect __watchdog_support. It belongs there. Do not mess with it.

 

To use the built-in watchdog, simply don't add

       WDTCTL = WDTPW + WDTHOLD;
anywhere. That turns the watchdog off. Instead, make sure you invoke __watchdog_clear() every 32ms.

 

Use __set_watchdog_clear_value() to assign a new value (WDTPW + something) that configures the interval you want; even if you do, still invoke __watchdog_clear() at the required frequency. Consult the family user's guide for details on how to determine alternative configuration values.

 

It might be an overkill for me right now to use these advanced stuff, but I would also like to know about all the other internal features that mspgcc has. Is there a documentation about them anywhere? I googled, but couldnt find anything regarding the watchdog functions you mentioned. The best I could find was http://mspgcc.sourceforge.net/manual/book1.html.

No; that manual is completely out of date and does not mention these functions. Look at intrinsics.h that was installed with the compiler, and experiment.

 

Probably nobody but me ever used those functions.

Share this post


Link to post
Share on other sites

Another thing to reducing the size is to use the -mdisable-watchdog flag. That gets rid of all extra WDT code.

Yes, that's the best solution if you want to pretend the MCU doesn't have a watchdog timer. It'll only work on mspgcc (not msp430-elf-gcc), and you'll save a whole 18 bytes of code and 2 bytes of RAM. ("Bloat"?)

 

Of course when all this fails to work, you can at least claim your code is small :)

Um, yes.

 

de gustibus non est disputandum and all that.

Share this post


Link to post
Share on other sites

I actually think the msp430-gcc 4.6.3 version produces pretty reasonable sized output. You can always throw inline asm at any code you really don't like. There are some other tricks you can use, like reserving a fixed register for use with the mainline code and some timing critical ISR to avoid register pushing and popping overhead. However, when you get to that point, you come to the realization that you are probably using the wrong chip.

 

I'm saddened that the new msp430-elf-gcc stuff truly does have bloat.

 

(BTW: the bloat comment was tongue in cheek in the posting above)

Share this post


Link to post
Share on other sites

Yeah, msp430-elf-gcc didn't turn out the way I'd hoped it would when I endorsed that initiative. The compiler is actually pretty good; the problems with expectations and device support aren't Red Hat's fault.

 

I'm really enjoying Cortex-M development, though again TI's software approach for Tiva comes up short relative to my quality expectations. Silicon Labs, though, is getting an A- for the EFM32 line.

 

(BTW: the bloat comment was tongue in cheek in the posting above)

I mostly figured it was, but I'm finding I'm a bit sensitive since I did put a lot of effort into making sure the CRT stuff in particular (which is in assembly) was both space/time efficient and conformed to the requirements of the language.

 

(For years Code Composer Studio didn't initialize global variables to zero because of a fear that the watchdog would fire while they were doing so, thus making their implementation of C completely non-conforming and requiring the application to explicitly initialize globals and statics---assuming it could somehow figure out that it hadn't already done so.)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×