2014年12月18日 星期四

作業系統之前的程式 for stm32f4discovery (12) - 實作 printf

之前寫了一篇關於 c printf 的不定個數參數的文章, 主要是針對 x86 (32 bit)。現在 cpu 換成 cm3, 不知道是不是也一樣。不過我無意間發現 gcc 有提供相應的 function 來處理這個問題。

不過這是作業系統之前的程式, 有個困難的問題, 得先搞定要輸出到哪裡? 這邊選用的是 uart, 嵌入式系統最常用到的 output, 所以要先把 uart 搞定, 還好我們很早以前就搞定

gcc 提供了 builtin function:
  • __builtin_va_start(ap,fmt);
  • __builtin_va_arg(ap, char *);
  • __builtin_va_end(ap);

我用了他們實作出一個 printf。L3 的 include 是必須的。

k_stdio.cpp
1 #include "k_stdio.h"
2 #include "k_string.h"
3 #include <stdarg.h>

362 int DS::printf(const char *fmt, ...)
363 {
364   va_list ap;
365 
366   char str[256];
367 
368   int d;
369   char c, *p, *s;  
370   char *cur_char = str;
371   int len = 0;
372 
373   __builtin_va_start(ap,fmt);
374   while(*fmt)
375   {
376     if (*fmt == '%')
377     {
378       ++fmt;
379       switch(*fmt)
380       {  
381         case 's':
382         {
383           s = __builtin_va_arg(ap, char *);  
384           len = s_strlen(s);
385           s_strcpy(cur_char, s);
386           cur_char += len;
387           break;
388         }
389         case 'd':
390         {
391           d = __builtin_va_arg(ap, int);
392           char num_str[10];
393           s32_itoa_s(d, num_str, 10);
394           len = s_strlen(num_str);
395           s_strcpy(cur_char, num_str);
396           cur_char += len;
397           break;
398         }
399         case 'c':
400         {
401           d = (char)__builtin_va_arg(ap, int);
402           *cur_char++ = d;
403           break;
404         }
405         default:
406         {
407           *cur_char++ = *fmt;
408         }
409       }
410     }
411     else
412     {
413       if (*fmt == '\n')
414       {
415         s_strcpy(cur_char, "\r\n");
416         cur_char += 2;
417       }
418       else
419         *cur_char++ = *fmt;
420     }
421     ++fmt;
422   }
423 
424   __builtin_va_end(ap);
425   *cur_char++ = 0;
426   len = s_strlen(str);
427   myprint(str);
428   return len;
429 }

可以正常的運作, 不算太難。但是這樣的黑箱函式並不能滿足我, 我想知道這 3 個 builtin function 到底施了什麼魔法, 怎麼挖開這黑箱呢? trace source code?? 不, 不需要和 libc/libgcc source code 奮鬥, 反組譯就可以揭開這黑箱。

但別著急, 我們先來看看參數是怎麼傳遞到這個不定個數參數的函式裡頭。

myp.cpp
 1 #include "stm32.h"
 2 
 3 #include <stdarg.h>
 4 
 5 int print(int i, int j)
 6 {
 7   return i+j;
 8 }
 9 
10 int myprintf(const char *fmt, ...)
11 {
12   va_list ap;
13   //int d;
14   char *cur;
15 
16   cur = (char *)&fmt;
17   cur += 4;
18   cur += 4;
19 
20 #if 0
21   __builtin_va_start(ap,fmt);
22   __builtin_va_arg(ap, char *);
23   __builtin_va_arg(ap, int);
24   __builtin_va_arg(ap, char *);
25   __builtin_va_arg(ap, char *);
26   __builtin_va_arg(ap, char *);
27   __builtin_va_arg(ap, char *);
28   __builtin_va_end(ap);
29 #endif
30   return 0;
31 }
32 
33 void mymain(void)
34 {
35   myprintf("abc", 1, 2);
36   print(1, 2);
37   myprintf("abc", 1, 2, 3, 4, 5, 6, 7, 8);
38 
39   while(1);
40 }

很有趣,
int print(int i, int j)
int myprintf(const char *fmt, ...)

這兩個函式在傳參數時, 使用了不同的方法, print 直接用 r0, r1 去接 i, j 的值, myprintf 則出動 stack, 終於破除了我的疑惑。arm 在傳遞 4 個以內的參數是用 register, 多餘的會用 stack, 而 gcc 在對付不定個數參數乾脆用 stack 來傳遞 (不過不是很直覺的那種), 我實在是太蠢了, 竟然想不出來。

arm-none-eabi-objdump
L169, L225~227: L169 在將 r1, r2 傳入 myprintf 時, 還會 push 一下到 stack, 這樣就相當於用 stack 來傳參數。
L233 ~ 247 則只有 r0, r1, r2, r3 用 stack 傳入, 超過四個參數就用 stack 傳入。
L229~231: 單純用 r0, r1 傳參數。

再來的東西就沒什麼秘密了, myp.cpp L16 ~ 18 是我用 +4 的方式從第一個參數得到其他參數。透過第一個 fmt 參數來把其他的參數抓出來, &fmt 抓出 fmt 的位址, 每一次 +4 就是第二個、第三個參數, 簡單容易, 不需要使用 gcc builtin function。那怎麼知道參數有幾個, 難道要一直 +4 下去嗎? 施主, 這就是你的問題了, 要不然 printf 那個 "%d %s" 是用來幹嘛的?

arm-none-eabi-objdump -S -C -d myp.elf
  1 
  2 myp.elf:     file format elf32-littlearm
  3 
  4 
  5 Disassembly of section .text:
  6 
  7 08000000 <VectorTable>:
  8  8000000: 00 01 00 20 41 00 00 08 d5 00 00 08 d5 00 00 08     ... A...........
  9  8000010: d5 00 00 08 d5 00 00 08 d5 00 00 08 d5 00 00 08     ................
 10  8000020: d5 00 00 08 d5 00 00 08 d5 00 00 08 bd 00 00 08     ................
 11  8000030: d5 00 00 08 d5 00 00 08 a1 00 00 08 c9 00 00 08     ................
 12 
 13 08000040 <ResetISR()>:
 14 {
 15   void mymain(void);
 16 }
 17 
 18 void ResetISR(void)
 19 {
 20  8000040: b580       push {r7, lr}
 21  8000042: b082       sub sp, #8
 22  8000044: af00       add r7, sp, #0
 23   unsigned long *pulSrc, *pulDest;
 24 
 25   pulSrc = &_etext;
 26  8000046: 4b11       ldr r3, [pc, #68] ; (800008c <ResetISR()+0x4c>)
 27  8000048: 607b       str r3, [r7, #4]
 28   for (pulDest = &_data; pulDest < &_edata;)
 29  800004a: 4b11       ldr r3, [pc, #68] ; (8000090 <ResetISR()+0x50>)
 30  800004c: 603b       str r3, [r7, #0]
 31  800004e: e007       b.n 8000060 <ResetISR()+0x20>
 32     *pulDest++ = *pulSrc++;
 33  8000050: 683b       ldr r3, [r7, #0]
 34  8000052: 1d1a       adds r2, r3, #4
 35  8000054: 603a       str r2, [r7, #0]
 36  8000056: 687a       ldr r2, [r7, #4]
 37  8000058: 1d11       adds r1, r2, #4
 38  800005a: 6079       str r1, [r7, #4]
 39  800005c: 6812       ldr r2, [r2, #0]
 40  800005e: 601a       str r2, [r3, #0]
 41 void ResetISR(void)
 42 {
 43   unsigned long *pulSrc, *pulDest;
 44 
 45   pulSrc = &_etext;
 46   for (pulDest = &_data; pulDest < &_edata;)
 47  8000060: 683a       ldr r2, [r7, #0]
 48  8000062: 4b0c       ldr r3, [pc, #48] ; (8000094 <ResetISR()+0x54>)
 49  8000064: 429a       cmp r2, r3
 50  8000066: d3f3       bcc.n 8000050 <ResetISR()+0x10>
 51     *pulDest++ = *pulSrc++;
 52   for (pulDest = &_bss; pulDest < &_ebss;)
 53  8000068: 4b0b       ldr r3, [pc, #44] ; (8000098 <ResetISR()+0x58>)
 54  800006a: 603b       str r3, [r7, #0]
 55  800006c: e004       b.n 8000078 <ResetISR()+0x38>
 56     *pulDest++ = 0;
 57  800006e: 683b       ldr r3, [r7, #0]
 58  8000070: 1d1a       adds r2, r3, #4
 59  8000072: 603a       str r2, [r7, #0]
 60  8000074: 2200       movs r2, #0
 61  8000076: 601a       str r2, [r3, #0]
 62   unsigned long *pulSrc, *pulDest;
 63 
 64   pulSrc = &_etext;
 65   for (pulDest = &_data; pulDest < &_edata;)
 66     *pulDest++ = *pulSrc++;
 67   for (pulDest = &_bss; pulDest < &_ebss;)
 68  8000078: 683a       ldr r2, [r7, #0]
 69  800007a: 4b08       ldr r3, [pc, #32] ; (800009c <ResetISR()+0x5c>)
 70  800007c: 429a       cmp r2, r3
 71  800007e: d3f6       bcc.n 800006e <ResetISR()+0x2e>
 72     *pulDest++ = 0;
 73 
 74 
 75   mymain();
 76  8000080: f000 f85e  bl 8000140 <mymain>
 77 }
 78  8000084: 3708       adds r7, #8
 79  8000086: 46bd       mov sp, r7
 80  8000088: bd80       pop {r7, pc}
 81  800008a: bf00       nop
 82  800008c: 08000184  .word 0x08000184
 83  8000090: 20000000  .word 0x20000000
 84  8000094: 20000000  .word 0x20000000
 85  8000098: 20000100  .word 0x20000100
 86  800009c: 20000100  .word 0x20000100
 87 

142 080000e0 <print(int, int)>:
143 #include "stm32.h"
144 
145 #include <stdarg.h>
146 
147 int print(int i, int j)
148 {
149  80000e0: b480       push {r7}
150  80000e2: b083       sub sp, #12
151  80000e4: af00       add r7, sp, #0
152  80000e6: 6078       str r0, [r7, #4]
153  80000e8: 6039       str r1, [r7, #0]
154   return i+j;
155  80000ea: 687a       ldr r2, [r7, #4]
156  80000ec: 683b       ldr r3, [r7, #0]
157  80000ee: 4413       add r3, r2
158 }
159  80000f0: 4618       mov r0, r3
160  80000f2: 370c       adds r7, #12
161  80000f4: 46bd       mov sp, r7
162  80000f6: f85d 7b04  ldr.w r7, [sp], #4
163  80000fa: 4770       bx lr
164 
165 080000fc <myprintf(char const*, ...)>:
166 
167 int myprintf(const char *fmt, ...)
168 {
169  80000fc: b40f       push {r0, r1, r2, r3}
170  80000fe: b480       push {r7}
171  8000100: b083       sub sp, #12
172  8000102: af00       add r7, sp, #0
173   va_list ap;
174   //int d;
175 
176   __builtin_va_start(ap,fmt);
177  8000104: f107 0314  add.w r3, r7, #20
178  8000108: 607b       str r3, [r7, #4]
                     相當於 ap = fmt; ap 位於 r7 + 4 的位址

179   __builtin_va_arg(ap, char *);
180  800010a: 687b       ldr r3, [r7, #4]
181  800010c: 3304       adds r3, #4
182  800010e: 607b       str r3, [r7, #4]
                     相當於 ap += 4;

183   __builtin_va_arg(ap, int);
184  8000110: 687b       ldr r3, [r7, #4]
185  8000112: 3304       adds r3, #4
186  8000114: 607b       str r3, [r7, #4]
187   __builtin_va_arg(ap, char *);
188  8000116: 687b       ldr r3, [r7, #4]
189  8000118: 3304       adds r3, #4
190  800011a: 607b       str r3, [r7, #4]
191   __builtin_va_arg(ap, char *);
192  800011c: 687b       ldr r3, [r7, #4]
193  800011e: 3304       adds r3, #4
194  8000120: 607b       str r3, [r7, #4]
195   __builtin_va_arg(ap, char *);
196  8000122: 687b       ldr r3, [r7, #4]
197  8000124: 3304       adds r3, #4
198  8000126: 607b       str r3, [r7, #4]
199   __builtin_va_arg(ap, char *);
200  8000128: 687b       ldr r3, [r7, #4]
201  800012a: 3304       adds r3, #4
202  800012c: 607b       str r3, [r7, #4]
203   __builtin_va_end(ap);
204 
205   return 0;
206  800012e: 2300       movs r3, #0
207 }
208  8000130: 4618       mov r0, r3
209  8000132: 370c       adds r7, #12
210  8000134: 46bd       mov sp, r7
211  8000136: f85d 7b04  ldr.w r7, [sp], #4
212  800013a: b004       add sp, #16
213  800013c: 4770       bx lr
214  800013e: bf00       nop
215 
216 08000140 <mymain>:
217 
218 void mymain(void)
219 {
220  8000140: b580       push {r7, lr}
221  8000142: b086       sub sp, #24
222  8000144: af06       add r7, sp, #24
223   myprintf("abc", 1, 2);
224  8000146: 480d       ldr r0, [pc, #52] ; (800017c <mymain+0x3c>)
225  8000148: 2101       movs r1, #1
226  800014a: 2202       movs r2, #2
227  800014c: f7ff ffd6  bl 80000fc <myprintf(char const*, ...)>
228   print(1, 2);
229  8000150: 2001       movs r0, #1
230  8000152: 2102       movs r1, #2
231  8000154: f7ff ffc4  bl 80000e0 <print(int, int)>
232   myprintf("abc", 1, 2, 3, 4, 5, 6, 7, 8);
233  8000158: 2304       movs r3, #4
234  800015a: 9300       str r3, [sp, #0]
235  800015c: 2305       movs r3, #5
236  800015e: 9301       str r3, [sp, #4]
237  8000160: 2306       movs r3, #6
238  8000162: 9302       str r3, [sp, #8]
239  8000164: 2307       movs r3, #7
240  8000166: 9303       str r3, [sp, #12]
241  8000168: 2308       movs r3, #8
242  800016a: 9304       str r3, [sp, #16]
243  800016c: 4803       ldr r0, [pc, #12] ; (800017c <mymain+0x3c>)
244  800016e: 2101       movs r1, #1
245  8000170: 2202       movs r2, #2
246  8000172: 2303       movs r3, #3
247  8000174: f7ff ffc2  bl 80000fc <myprintf(char const*, ...)>
248   __builtin_va_end(ap);
249 
250   return 0;
251 }
252 
253 void mymain(void)
254  8000178: e7fe       b.n 8000178 <mymain+0x38>
255  800017a: bf00       nop
256  800017c: 08000180  .word 0x08000180
257  8000180: 00636261  .word 0x00636261

L176~182 則是分析 __builtin_va_arg(ap, char *); 的組合語言, 我標上了注釋, 應該很簡單, 其實也只是 +4 的行為。

source code:
https://github.com/descent/stm32f4_prog/blob/master/cpp/myp.cpp

不過還是使用

  • __builtin_va_start(ap,fmt);
  • __builtin_va_arg(ap, char *);
  • __builtin_va_end(ap);

容易多了, 也不用辛苦去搞懂 gcc 在不同平台的參數傳遞方式。有興趣的朋友可以試試看 x86/x64 的版本會如何。

藉由 c++ 的 namespace, 我不用取個 myprintf 的名字, DS::printf 看來痛快多了。本篇的範例是 c++ 程式, 別看到 printf 就以為是 c 寫的。

透過 qemu-system-arm -M lm3s6965evb -kernel myp.bin -S -gdb tcp::1234 來測試

ref: Use printf to output stream on STM32F4 (by jserv)
以下影片是整個執行過程:

4 則留言:

  1. FYI,
    http://stm32f4-discovery.com/ 的 URL 需要更新成 https://stm32f4-discover.net/
    Thanks.

    回覆刪除
    回覆
    1. Hi James Yang,

      謝謝, 已經修正。

      刪除
    2. 謝謝您詳盡的筆記.... 這幾天都被黏在您的網誌上..... 後生可畏啊....

      刪除
    3. 很高興你喜歡這些內容。我年紀不小了, 不一定是後生。XD

      刪除

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。