This document provides an introduction and examples showing how to do profiling and test coverage analysis, and it also provides some of the technical details on how all this works. We're covering two sets of related functionalities here :
This document doesn't duplicate their documentation, so please consult the man pages of gprof and gcov for their description.
Profiling is triggered by using the compiler with the -pg command line option, a program compiled with it will generate a single file calles gmon.out. You can decode the gmon.out file by using the gprof tool.
This Makefile has pieces that can be copied in your own project's Makefile. It uses the pcp, prm, and prun commands from the Synce project.
.SUFFIXES: .exe .c .h .rc .rsc .C ARCH= arm-wince-cegcc CC= ${ARCH}-gcc ${CFLAGS} CFLAGS= -g -D_WIN32_IE=0x0400 -pg .o.exe: ${ARCH}-gcc ${CFLAGS} -o $@ $? .c.exe: ${ARCH}-gcc ${CFLAGS} -o $@ $? .C.exe: ${ARCH}-g++ ${CFLAGS} -o $@ $? all:: fibo.exe fibo.explain clean: -rm -f *.o *.exe *.rsc gmon.out *.explain *.gmon dist: pcp fibo.exe ":/storage card/devel/fibo.exe" fibo.explain: fibo.gmon fibo.exe ${ARCH}-gprof -q -x -l fibo.exe fibo.gmon > fibo.explain fibo.gmon: fibo.exe pcp fibo.exe ":/storage card/devel/fibo.exe" prun "/storage card/devel/fibo.exe" sleep 5 pcp ":/storage card/devel/gmon.out" fibo.gmon prm "/storage card/devel/gmon.out"
When you run make, this happens :
dannypc: {10} make arm-wince-cegcc-gcc -g -D_WIN32_IE=0x0400 -pg -o fibo.exe fibo.c Info: resolving _CRT_MT by linking to __imp__CRT_MT (auto-import) pcp fibo.exe ":/storage card/devel/fibo.exe" File copy of 31491 bytes took 0 minutes and 1 seconds, that's 31491 bytes/s. prun "/storage card/devel/fibo.exe" sleep 5 pcp ":/storage card/devel/gmon.out" fibo.gmon File copy took less than one second! prm "/storage card/devel/gmon.out" arm-wince-cegcc-gprof -q -x -l fibo.exe fibo.gmon > fibo.explain dannypc: {11}
The top of the fibo.explain file looks like this :
Call graph (explanation follows) granularity: each sample hit covers 4 byte(s) no time propagated index % time self children called name 0.00 0.00 19/21871 main (fibo.c:34 @ 11238) [45] 0.00 0.00 21852/21871 fibo (fibo.c:12 @ 110e8) [28] [1] 0.0 0.00 0.00 21871 fibo (fibo.c:9 @ 110b0) [1]
Run the commands for yourself and read the complete explanation in the output file.
Test coverage generates far more interesting results. It is triggered by several options to the compiler. An easy way to get started is to use --coverage.
An application built with this option will create a .gcda file for each source for compiled in the application. You can use the gcov tool to decode the contents of these files.
.SUFFIXES: .exe .c .h .rc .rsc .C ARCH= arm-wince-cegcc ARCH2= arm-wince-mingw32ce SE= GCOV_CROSS_PREFIX="\\storage card\\devel" CC= ${SE} ${ARCH}-gcc ${CFLAGS} WINDRES= ${ARCH}-windres EXECS= fibo.exe m1.exe CFLAGS= -g -D_WIN32_IE=0x0400 --coverage .rc.rsc: ${ARCH}-windres $? $@ .o.exe: ${SE} ${ARCH}-gcc ${CFLAGS} -o $@ $? .c.exe: ${SE} ${ARCH}-gcc ${CFLAGS} -o $@ $? .C.exe: ${SE} ${ARCH}-g++ ${CFLAGS} -o $@ $? all:: ${EXECS} clean: -rm -f *.o *.exe *.rsc *.gcno *.gcda *.gcov dist: for i in $(EXECS) ; \ do \ pcp $$i ":/storage card/devel/"$$i ; \ done fibo.gcda: fibo.exe pcp fibo.exe ":/storage card/devel/fibo.exe" -prm "/storage card/devel/fibo.gcda" prun "/storage card/devel/fibo.exe" sleep 5 pcp ":/storage card/devel/fibo.gcda" fibo.gcda -prm "/storage card/devel/fibo.gcda" fibo.c.gcov: fibo.gcda arm-wince-cegcc-gcov -a fibo.c all:: fibo.c.gcov # # The m1.exe example uses the mingw32ce target, # and demonstrates multiple source files. # m1.exe: m1.o a.o b.o ${SE} ${ARCH2}-gcc ${CFLAGS} -o m1.exe a.o b.o m1.o m1.o: m1.c ${SE} ${ARCH2}-gcc ${CFLAGS} -c m1.c a.o: a.c ${SE} ${ARCH2}-gcc ${CFLAGS} -c a.c b.o: b.c ${SE} ${ARCH2}-gcc ${CFLAGS} -c b.c a.gcda b.gcda m1.gcda: m1.exe pcp m1.exe ":/storage card/devel/m1.exe" -prm "/storage card/devel/m1.gcda" -prm "/storage card/devel/a.gcda" -prm "/storage card/devel/b.gcda" prun "/storage card/devel/m1.exe" sleep 5 pcp ":/storage card/devel/m1.gcda" m1.gcda pcp ":/storage card/devel/a.gcda" a.gcda pcp ":/storage card/devel/b.gcda" b.gcda -prm "/storage card/devel/m1.gcda" -prm "/storage card/devel/a.gcda" -prm "/storage card/devel/b.gcda" m1.c.gcov: m1.gcda arm-wince-cegcc-gcov -a m1.c a.c.gcov: a.gcda arm-wince-cegcc-gcov -a a.c b.c.gcov: b.gcda arm-wince-cegcc-gcov -a b.c all:: m1.c.gcov a.c.gcov b.c.gcov
dannypc: {33} make GCOV_CROSS_PREFIX="\\storage card\\devel" arm-wince-cegcc-gcc -g -D_WIN32_IE=0x0400 --coverage -o fibo.exe fibo.c Info: resolving _CRT_MT by linking to __imp__CRT_MT (auto-import) GCOV_CROSS_PREFIX="\\storage card\\devel" arm-wince-mingw32ce-gcc -g -D_WIN32_IE=0x0400 --coverage -c m1.c GCOV_CROSS_PREFIX="\\storage card\\devel" arm-wince-mingw32ce-gcc -g -D_WIN32_IE=0x0400 --coverage -c a.c GCOV_CROSS_PREFIX="\\storage card\\devel" arm-wince-mingw32ce-gcc -g -D_WIN32_IE=0x0400 --coverage -c b.c GCOV_CROSS_PREFIX="\\storage card\\devel" arm-wince-mingw32ce-gcc -g -D_WIN32_IE=0x0400 --coverage -o m1.exe a.o b.o m1.o pcp fibo.exe ":/storage card/devel/fibo.exe" File copy took less than one second! prm "/storage card/devel/fibo.gcda" prm: Failed to remove '\storage card\devel\fibo.gcda': File not found gmake: [fibo.gcda] Error 1 (ignored) prun "/storage card/devel/fibo.exe" sleep 5 pcp ":/storage card/devel/fibo.gcda" fibo.gcda File copy took less than one second! prm "/storage card/devel/fibo.gcda" arm-wince-cegcc-gcov -a fibo.c File 'fibo.c' Lines executed:100.00% of 20 fibo.c:creating 'fibo.c.gcov' pcp m1.exe ":/storage card/devel/m1.exe" File copy of 39012 bytes took 0 minutes and 1 seconds, that's 39012 bytes/s. prm "/storage card/devel/m1.gcda" prm: Failed to remove '\storage card\devel\m1.gcda': File not found gmake: [m1.gcda] Error 1 (ignored) prm "/storage card/devel/a.gcda" prm: Failed to remove '\storage card\devel\a.gcda': File not found gmake: [m1.gcda] Error 1 (ignored) prm "/storage card/devel/b.gcda" prm: Failed to remove '\storage card\devel\b.gcda': File not found gmake: [m1.gcda] Error 1 (ignored) prun "/storage card/devel/m1.exe" sleep 5 pcp ":/storage card/devel/m1.gcda" m1.gcda File copy took less than one second! pcp ":/storage card/devel/a.gcda" a.gcda File copy took less than one second! pcp ":/storage card/devel/b.gcda" b.gcda File copy took less than one second! prm "/storage card/devel/m1.gcda" prm "/storage card/devel/a.gcda" prm "/storage card/devel/b.gcda" arm-wince-cegcc-gcov -a m1.c File '/opt/mingw32ce/lib/gcc/arm-wince-mingw32ce/4.1.0/../../../../arm-wince-mingw32ce/include/string.h' Lines executed:0.00% of 3 /opt/mingw32ce/lib/gcc/arm-wince-mingw32ce/4.1.0/../../../../arm-wince-mingw32ce/include/string.h:creating 'string.h.gcov' File '/opt/mingw32ce/lib/gcc/arm-wince-mingw32ce/4.1.0/../../../../arm-wince-mingw32ce/include/stdlib.h' Lines executed:0.00% of 5 /opt/mingw32ce/lib/gcc/arm-wince-mingw32ce/4.1.0/../../../../arm-wince-mingw32ce/include/stdlib.h:creating 'stdlib.h.gcov' File 'm1.c' Lines executed:100.00% of 10 m1.c:creating 'm1.c.gcov' arm-wince-cegcc-gcov -a a.c File 'a.c' Lines executed:100.00% of 3 a.c:creating 'a.c.gcov' arm-wince-cegcc-gcov -a b.c File 'b.c' Lines executed:100.00% of 6 b.c:creating 'b.c.gcov' dannypc: {34}
The output above includes some interesting results already : it mentions the amount of source code executed per source file. The contents of the .gcov files are even more interesting. This is the contents of b.c.gcov :
-: 0:Source:b.c -: 0:Graph:b.gcno -: 0:Data:b.gcda -: 0:Runs:1 -: 0:Programs:1 -: 1:/* -: 2: * Profiling test -: 3: */ -: 4:int fibo(int x) 2692507: 5:{ 2692507: 6: if (x < 3) 2692507: 6-block 0 1346268: 7: return 1; 1346268: 7-block 0 2692507: 8: return fibo(x-1) + fibo(x-2); 1346239: 8-block 0 2692507: 8-block 1 -: 9:} -: 10: -: 11:int func_b(int i) 29: 12:{ 29: 13: return fibo(i); 29: 13-block 0 -: 14:}
The seven digit numbers in the first column are the number of times these source lines
were executed.
Modify m1.c so fibo() is called fewer times,
and you'll see more modest numbers.
2 Technical overview : CeGCC (gcc) profiling and coverage support
This piece of documentation is not strictly related to profiling and test coverage support, the normal operation is also described.
The tables below present an overview of how this all works, the details are described further in this document.
The behaviour of the arm-wince-cegcc-gcc compiler :
cegcc target | Compiler option | Startup files | Startup calls | Any source calls | Exit calls (by atexit) |
Normal | crt0.o crtst.o | N/A | N/A | N/A | |
Profiling | -pg | crt0.o crtst.o gcrt3.o | monstartup | mcount | mcleanup |
Coverage | --coverage | crt0.o crtst.o | N/A | _gcov_init | gcov_exit |
The behaviour of the arm-wince-mingw32ce-gcc compiler :
mingw32ce target | Compiler option | Startup files | Startup calls | Any source calls | Exit calls (by atexit) |
Normal | crt3.o | N/A | N/A | N/A | |
Profiling | -pg | crt3.o gcrt3.o | monstartup | mcount | mcleanup |
Coverage | --coverage | crt3.o | N/A | _gcov_init | gcov_exit |
Profiling support depends on special startup code (in gcrt3.o) and an exit handler (mcleanup) installed by it.
The source of gcrt3.o is in src/mingw/profile/gcrt0.c. It provides for the function _monstartup whose purpose is to call monstartup and register _mcleanup as an exit handler via atexit.
Both _mcleanup and monstartup are in libgmon.a, their source is src/mingw/profile/gmon.c.
For gcov support, there is no special startup function,
rather the compiler generates a call to _gcov_init from all compiled source files.
The support functions are in src/gcc/gcc/libgcov.c
and src/gcc/gcc/gcov-io.c.
2.4 Ways in which WinCE applications start and terminate
main is the entry point for C language programs in college textbooks, it's also the default entry point generated by cegcc. crt0.o will call main.
The normal startup for an application with a GUI (graphical user interface) on a Windows platform is WinMain. CeGCC implements that through the main.o module in libc.a or libcegcc.dll.
The main.o module defines a main function which calls WinMain. To figure out the parameters for that function, it calls a number of WinCE functions such as GetCommandLineW. When you compile a program that has a main function of its own (see the previous paragraph), the main.o is simply not used. The source for main.o is in src/newlib/newlib/libc/sys/wince/main.c.
This chapter describes the various ways in which a Windows CE application can terminate.
The WM_DESTROY message is sent to an application by the system e.g. when the user hits the button to close that window. The typical response of an application is to call PostQuitMessage.
The PostQuitMessage function posts a request to terminate this thread or application. It does so by sending a WM_QUIT message to the thread or application.
In response to WM_QUIT, the normal message loop will terminate.
BOOL r; while ((r = GetMessageW(&msg, wnd, 0, 0)) != 0) { if (r != -1) { TranslateMessage(&msg); DispatchMessageW(&msg); } else { // Handle the error } }
This is implemented in the GetMessageW function : when the function receives the WM_QUIT message, its return value is 0.
In our arm-wince-cegcc target, this is implemented in the system layer of newlib. Startup and termination are both coded in src/newlib/newlib/libc/sys/wince/startup.c.
The exit function (part of the stdlib part of newlib, in src/newlib/newlib/libc/stdlib/) does little more than calling _exit. The latter function jumps back into the _startup_ function by using longjmp and performs newlib cleanup, and finally calls ExitProcess which is a Windows CE System Call.
In our arm-wince-mingw32ce target, exit is implemented in src/mingw/atexit.c. It calls a cleanup function and ExitProcess.
On arm-wince-cegcc, falling through the end of main gets us to the same place where calling exit leads to. On arm-wince-mingw32ce, this is not true, but the startup function (WinMainCRTStartup, e.g. in src/mingw/crt3.c) calls the same functions as exit.
Information |
Support |