芯片IC單片機解密百科

十年專注單片機解密

單片機破解實操經驗-如何在STM32上移植Linux代碼語句

之前跑的是ok6410的板子上運行的linux,現在是在stm32上移植,以前stm32倒是玩過,研究生期間就搗鼓過它,但現在還沒從抓烙鐵的硬件當中緩過神來,就轉到嵌入式軟件的開發,更頭疼的是stm32沒有MMU!沒有MMU!找了一下,好吧,有個uClinux!


於是開始學習各種相關的知識,了解到linux的啟動一般是u-boot——》liunx內核——》根文件係統,那麽首先要做個基於stm32的u-boot,先初始化時鍾、外設、中斷什麽的,看了韋東山老師的視頻感覺很好,理解了不少,從一無所知到有點明白了。


移植u-boot到stm32f407

其實說白了u-boot就是一裸板程序,就是跟跑跑馬燈、串口通信一個性質的,而裸板程序從正點原子的stm32開發板學習了不少,加上自己研究生階段有點積累,首先我是參照http://www.cnblogs.com/fozu/p/3618076.html寫這位博文的大神寫的程序,這篇文章寫得很好,後麵還分析到內核了,反複看受益匪淺,這個程序不是u-boot程序但是實現的作用一樣,初始化時鍾,外設。。。最後傳遞內核參數,跳轉內核。。。,一開始用keil編譯這個程序,結果一堆錯誤,人家用的板子和你用的板子不一樣,硬件的led燈、串口都可能連接不一樣啊,比如人家用的是串口1,你用的是串口2,還有缺少一些頭文件等等都會引發錯誤,所以根據自己的實際來修改,費了一陣功夫終於把錯誤全幹掉,順利編譯成功。


這時用的板子是stm32f103,ST對這個板子早在08年就發布了支持它的u-boot、Uclinux內核(領導額外買的,說是要我對照著對應修改支持stm32f407的uClinux內核),但是隻有Uclinux內核有源碼,u-boot就給了個hex文件尷尬,其實cortx m3與cortx m4之前架構已經大不一樣了,這樣修改的話對於我來說無疑是很難的,我一聽頭都大了,又是單幹,煩,沒辦法照做唄!那就先弄stm32f103的,把之前那個編譯沒有錯誤的引導程序拷入,在stm32的0x08003000的位置拷入官方提供的uClinux內核,一啟動,接上串口,打開串口助手,一看啥都沒有。。。


到底錯在哪啦?仔細想想,先是要看看最後跳轉內核那步到底有沒有成功,那就先驗證這一步,參照原子的IAR跳轉曆程,編了個跑馬燈跳轉程序,就是引導程序沒變,拷在地址0x08000000,而跑馬燈程序拷在0x08003000上,如果led燈亮滅就說明跳轉無誤,於是一啟動,燈不亮。抓狂抓狂怎麽情況啊,後仔細排查發現是跳轉函數,引導程序參照的是u-boot源碼來編寫的,裏麵的函數用函數指針賦個地址(0x08003000),最後跳轉過去。折騰了兩天最後對著原子的程序修改,燈居然可以亮滅了,我現在想想也不知道是什麽問題,不過至少現在可以實現跳轉了。


再把內核拷到0x08003000,一啟動,單片機破解串口助手還是沒有任何輸出,這下就真的煩了,鬱悶死了,stm32f103還搞不定還想搞stm32f407。。。之後開始各種找原因,各種修改,領導各種催,在stm32f103和stm32f407兩個板子之間這搞搞,那搞搞,休息時間就看看韋老師的視頻,找資料看看有什麽靈感,但是還是沒什麽進展。


後來在網上搜到一個哥們居然在stm32f407上移植u-boot成功了,而且還有啟動圖曬出來,這下我就想,人家可以我為什麽就不能?於是繼續找,終於在網上找到了這個u-boot的源碼,根據自己的stm32f407的板子修改串口,時鍾等,安裝好對應的交叉編譯鏈,注意應該是arm-non-eabi不帶linux的,因為是裸板程序不關linux啥事,然後一跑,終於在串口助手看到久違的u-boot啟動圖,狂喜!想想那段日子確實是在壓力之下成長的,感覺技術上有了很大的提升了。


領導過來一看見有u-boot(有點東西交差了。。。)就說要把外部的SRAM驅動加上,以便於跑linux內核,這個sram隻有512K,這麽小能跑得了linux內核嗎?這是後話,先把sram驅動加到u-boot上再說。


先參照原子的sram程序修改運行試試看看,結果可以運行但是寫入再讀出,有幾個地址的數據總有錯誤,於是一直苦思冥想,想到了一個可能,驅動外部SRAM用到的是stm32的FSMC配置,它有btcr寄存器設置,分為bcr和btr設置,原子的開發板用的是1M16位的,而我的是512k8位,在btr寄存器設置那裏應該是設成8位而不是16位,於是把相關設置位置0,這下數據正常了。


接下來就是在u-boot上添加sram的驅動,這個u-boot編寫得還蠻好,不過它配置的是外部8M的SDRAM,那麽我就在sdram_init()的函數上添加配置sram的代碼,把原來配置sdram的代碼通通刪去。折騰了兩天,編寫修改成功,一開機,串口助手正常輸出啟動信息,用u-boot的md、mw指令驗證sram的驅動是否可行,之間也遇到一些問題,如在前100個地址寫ff,md查看有幾個地址數據不對,不是顯示ff,用之前的sram裸板程序也是如此,一想軟件程序肯定是沒問題,那就是硬件問題,幸虧還搞過一段時間硬件,不然被公司硬件工程師給坑了,用萬用表仔細檢測,果然發現sram有幾個數據線虛焊了,怪不得數據有誤,拿烙鐵一拖,OK!數據正常了,嗯!想成為合格的嵌入式軟件工程師還是要軟件硬件相結合,不能脫離了硬件啊!!!!


好!至此基於stm32f407的u-boot移植成功,外加外部2M的SRAM驅動(後來把512K升級為2M,因為後來內核內存不夠跑到一半kernel panic掛了,此乃後話),最後上一張u-boot啟動圖。人生第一篇在CSDN的博文,希望以後自己不斷學習技術不斷提升,努力!

 

移植uClinux內核到stm32f407

上麵介紹了先移植基於stm32f407的u-boot,下麵會講到其中最難的移植stm32f407的內核這部分,這個內核源代碼我也是在網上找到了,看介紹是國外大神修改而成的,真的萬分感謝這位大神,網上的資源其實很多,要善於挖掘,善於搜尋。


內核代碼是我無意中down下來的,剛得到代碼時並沒有對在stm32f407上跑uClinux有太多的信心,一是網上還沒有在stm32f407跑uClinux的資料(至少我沒找到過)網上都對在stm32上跑uClinux都是唱衰的態度,的確stm32跑起uClinux係統,資源是有些匱乏,而stm32f407內部flash隻有1M的空間,其中u-boot占了128K,那麽內核就存儲在0x08020000處,剩下900k的空間使用,還有我的板子還有外部2M 的SRAM,但更要命的是得到的代碼是基於stm32f429的uClinux,很多人都在stm32f429上成功運行了,但是卻從沒在stm32f407成功過,但我已經沒有退路了,項目需要、領導要求,隻能硬著頭皮瞎改,其實對於stm32f103改成stm32f429已經好很多了,最起碼stm32f429的架構和stm32f407的架構大致相同(內部存儲和時鍾和gpio等略有不同),於是就按照自己手上的板子來改,期間遇到了不少的問題,也想過放棄,不過好歹堅持了下來,因為著急壓力山大所以看了不少書,查了很多資料也學到了很多東西對u-boot和內核代碼有了深入的理解,


特別感謝的是jserv老師,我走投無路之下給他發了幾封郵件,他回答了我兩個極為重要的問題,建議把外部的512K換成至少2M的SRAM,不然內核就真的跑不動了,跑到一半就kernel panic….


然後就是針對stm32f407來修改內核代碼,stm32f429用的是串口3,我用的是串口1,改!時鍾不對,改!儲存地址不同,改!stm32f429不單是有外部的SRAM,空間8M還有NOR flash,財大氣粗,資源隨便用,不像我的stm32f407隻有外部2M的SRAM(領導說硬件就那樣,節約成本,無語。。),幸好uClinux代碼是用XIP的方式來運行的,就是代碼段放在內部flash中就地執行,數據段和bss段其它段就放在sram上運行,這樣算算,空間還是足夠的。


其間還出現這樣的問題:

卡了我一個星期,當時我就百思不得其解,在創建高速緩存那裏就出現內核錯誤運行不下去了,仔細比對了stm32f103的uClinux源代碼,也沒發現什麽錯誤,一個多星期沒有進展,內核恐慌我也恐慌了,幸好領導知道情況後也不催促我,而是買了一本《ARM Linux內核源代碼分析》給我,叫我好好研讀,解決問題,於是就看裏麵構建kmem cache那一篇,linux內核源碼過於複雜,看得我頭都大了,後來想想這不是辦法啊,是不是又是硬件問題?因為原先用的是512k的sram升級到2M,公司的硬件工程師又重新改版了,於是我又用電烙鐵把stm32芯片,sram芯片,和他們之間的上拉電阻,又重新焊了一遍,一上電就正常運行到下一步了,唉~之前移植u-boot的sram驅動也是硬件坑我的啊,真不敢相信我不懂點硬件的話會坑到我什麽時候。。。


  接著瞎搗鼓著,前後花了將近兩個月,就成這樣了:

- 想想還真是運氣好。。。


接下來遇到的問題,應該是少了根文件係統,這個uClinux代碼原來是配有根文件係統的,是romfs,但是存儲空間不夠了。


uClinux的根文件係統未能掛載起來,因為係統原來配置的根文件係統是romfs,是基於stm32f429的,stm32f429的內部flash存儲空間有2M,romfs占用空間為300多kB,這樣存放顯然是充足的,但是對於stm32f407來說,它的內部flash存儲空間為1M,這樣存放的話,存儲空間是不夠的(u-boot占用空間0x08000000-0x08020000,內核占用空間約為0x08020000-0x080BB000,約620多KB,那麽隻有剩下約250多KB的空間供根文件係統存放),所以根據這個情況,我想是另外搭建占用內存空間更小的initramfs作為uClinux的根文件係統來掛載。


構建stm32f407-uClinux的initramfs根文件係統

上文講到內核運行到free init memory:8k這個地方就卡住,運行不下去了,在查閱了相關資料後,推測是缺少根文件係統所導致的,原來的內核源代碼是搭配有根文件係統的bin文件,是romfs但沒有源碼,前麵講過我現在項目使用的是stm32f407,內部flash容量和外部SRAM都不足以拷入這個原配的romfs掛起為根文件係統來使用。


接下來就是尋找一種經濟適用的文件係統來作為內核的根文件係統,從網上查閱相關資料可以知道,YAFFS2支持的是nandflash,jffs支持nor flash,這些看來對於我手上的stm32f407來說是不適用的,於是我仔細研究了stm32f103的源代碼,發現它是有兩種啟動的方式,一種采用的是用iniramfs作為根文件係統,xip啟動,在stm32f103內部flash隻有512k的情況居然跑起了Uclinux,另一種是jfss2掛在外部nor flash上,顯然我這種情況目前隻有參考第一種方式來,采用initramfs作為根文件係統。於是開始構建initramfs相關文件。 仔細研究stm32f103 XIP啟動方式的內核配置make menuconfig中, CONFIG_INITRAMFS_SOURCE=”initramfs-filelist” 而initramfs-filelist位於Uclinux/linux-2.6.x下,打開一看是這樣的:

 

一開始我看不懂這裏麵的shell什麽意思,網上找到一篇文章,寫得很清楚,把它copy過來,學習一下:

 

把initramfs編譯到內核裏麵去

使用initramfs最簡單的方式,莫過於用已經做好的cpio.gz把kernel裏麵那個空的給換掉。這是2.6 kernel天生支持的,所以,你不用做什麽特殊的設置。


kernel的config option裏麵有一項CONFIG_INITRAMFS_SOURCE(I.E. General setup—>Initramfs source file(s) in menuconfig)。這個選項指向放著內核打包initramfs需要的所有文件。默認情況下,這個選項是留空的,所以內核編譯出來之後initramfs也就是空的,也就是前麵提到的rootfs什麽都不做的情形。


CONFIG_INITRAMFS_SOURCE 可以是一個絕對路徑,也可以是一個從kernel’s top build dir(你敲入build或者是make的地方)開始的相對路徑。而指向的目標可以有以下三種:一個已經做好的cpio.gz,或者一個已經為製作cpio.gz準備好所有內容的文件夾,或者是一個text的配置文件。第三種方式是最靈活的,BBIN导航先依次來介紹這三種方法。


1)使用一個已經做好的cpio.gz檔案

If you already have your own initramfs_data.cpio.gz file (because you created it yourself, or saved the cpio.gz file produced by a previous kernel build), you can point CONFIG_INITRAMFS_SOURCE at it and the kernel build will autodetect the file type and link it into the resulting kernel image.


You can also leave CONFIG_INITRAMFS_SOURCE empty, and instead copy your cpio.gz file to usr/initramfs_data.cpio.gz in your kernel’s build directory. The kernel’s makefile won’t generate a new archive if it doesn’t need to.


Either way, if you build a kernel like this you can boot it without supplying an external initrd image, and it’ll still finish its boot by running your init program out of rootfs. This is packaging method #2, if you’d like to try it now.


2)指定給內核一個文件或者文件夾

If CONFIG_INITRAMFS_SOURCE points to a directory, the kernel will archive it up for you. This is a very easy way to create an initramfs archive, and is method #3.


Interestingly, the kernel build doesn’t use the standard cpio command to create initramfs archives. You don’t even need to have any cpio tools installed on your build system. Instead the kernel build (in usr/Makefile) generates a text file describing the directory with the script “gen_initramfs_list.sh”, and then feeds that descript to a program called “gen_init_cpio” (built from C source in the kernel’s usr directory), which create the cpio archive. This looks something like the following:


scripts/gen_initramfs_list.sh $CONFIG_INITRAMFS_SOURCE > usr/initramfs_list
usr/gen_init_cpio usr/initramfs_list > usr/initramfs_data.cpio
gzip usr/initramfs_data.cpio


To package up our hello world program, you could simply copy it into its own directory, name it “init”, point CONFIG_INITRAMFS_SOURCE at that directory, and rebuild the kernel. The resulting kernel should end its boot by printing “hello world”. And if you need to tweak the contents of that directory, rebuilding the kernel will re-package the contents of that directory if anything has changed.


The downside of this method is that it if your initramfs has device nodes, or cares about file ownership and permissions, you need to be able to create those things in a directory for it to copy. This is hard to do if you haven’t got root access, or are using a cross-compile environment like cygwin. That’s where the fourth and final method comes in.


3)使用configuration文件initramfs_list來告訴內核initramfs在哪裏

This is the most flexible method. The kernel’s gen_initramfs_list.sh script creates a text description file listing the contents of initramfs, and gen_init_cpio uses this file to produce an archive. This file is a standard text file, easily editable, containing one line per file. Each line starts with a keyword indicating what type of entry it describes.


The config file to create our “hello world” initramfs only needs a single line:

 file /init usr/hello 500 0 0

This takes the file “hello” and packages it so it shows up as /init in rootfs, with permissions 500, with uid and gid 0. It expects to find the source file “hello” in a “usr” subdirectory under the kernel’s build directory. (If you’re building the kernel in a different directory than the source directory, this path would be relative to the build directory, not the source directory.)


To try it yourself, copy “hello” into usr in the kernel’s build directory, copy the above configuration line to its own file, use “make menuconfig” to point CONFIG_INITRAMFS_SOURCE to that file, run the kernel build, and test boot the new kernel. Alternately, you can put the “hello” file in its own directory and use “scripts/gen_initramfs_list.sh dirname” to create a configuration file (where dirname is the path to your directory, from the kernel’s build directory). For large projects, you may want to generate a starting configuration with the script, and then customize it with any text editor.


This configuration file can also specify device nodes (with the “nod” keyword), directories (“dir”), symbolic links (“slink”), named FIFO pipes (“pipe”), and unix domain sockets (“sock”). Full documentation on this file’s format is available by running “usr/gen_init_cpio” (with no arguments) after a kernel build.

A more complicated example containing device nodes and symlinks could look like this:


dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
One significant advantage of the configuration file method is that any regular user can create one, specifying ownership and permissions and the creation of device nodes in initramfs, without any special permissions on the build system. Creating a cpio archive using the cpio command line tool, or pointing the kernel build at a directory, requires a directory that contains everything initramfs will contain. The configuration file method merely requires a few source files to get data from, and a description file.

This also comes in handy cross-compiling from other environments such as cygwin, where the local filesystem may not even be capable of reproducing everything initramfs should have in it.


總結一下

這四種給rootfs提供內容的方式都有一個共同點:在kernel啟動時,一係列的文件被解壓到rootfs,如果kernel能在其中找到可執行的文件“/init”,kernel就會運行它;這意味著,kernel不會再去理會“root=”是指向哪裏的。


此外,一旦initramfs裏麵的init 進程運行起來,kernel就會認為啟動已經完成。接下來,init將掌控整個宇宙!它擁有霹靂無敵的專門為它預留的Process ID #1,整個係統接下來的所有都將由它來創造!還有,它的地位將是不可剝奪的,嗯哼,PID 1 退出的話,係統會panic的。


接下來我會介紹其他一些,在rootfs中,init程序可以做的事。


Footnote 1: The kernel doesn’t allow rootfs to be unmounted for the same reason the same reason it won’t let the first process (PID 1, generally running init) be killed. The fact the lists of mounts and processes are never empty simplifies the kernel’s implementation.

Footnote 2: The cpio format is another way of combining files together, like tar and zip. It’s an older and simpler storage format that dates back to the original unix, and it’s the storage format used inside RPM packages. It’s not as widely used as tar or zip because the command line syntax of the cpio command is unnecessarily complicated (type “man 1 cpio” at a Linux or Cygwin command line if you have a strong stomach). Luckily, we don’t need to use this command.

Footnote 3: The kernel will always panic if PID 1 exits; this is unrelated to initramfs. All of the signals that might kill init are blocked, even “kill -9” which will reliably kill any other process. But init can still call the exit() syscall itself, and the kernel panics if this happens in PID 1. Avoiding it here is mostly a cosmetic issue: we don’t want the panic scrolling our “Hello World!” message off the top of the screen.


Footnote 4: Statically linking programs against glibc produces enormous, bloated binaries. Yes, this is expected to be over 400k for a hello world proram. You can try using the “strip” command on the resulting binary, but it won’t help much. This sort of bloat is why uClibc exists.


Footnote 5: Older 2.6 kernels had a bug where they would append to duplicate files rather than overwriting. Test your kernel version before depending on this behavior.


Footnote 6:User Mode Linux or QEMU can be very helpful testing out initramfs, but are beyond the scope of this article.

Footnote 7: Well, sort of. The default one is probably meant to be empty, but due to a small bug (gen_initramfs_list.sh spits out an example file when run with no arguments) the version in the 2.6.16 kernel actually contains a “/dev/console” node and a “/root” directory, which aren’t used for anything. It gzips down to about 135 bytes, and might as well actually be empty. On Intel you can run “readelf -S vmlinux” and look for section “.init.ramfs” to see the cpio.gz archive linked into a 2.6 kernel. Elf section names might vary a bit on other platforms.

 

顯然stm32f103使用的是第三種方法,裏麵那些指令無非是設置文件權限、設置軟連接等等操作,期間還學了一點bash shell,又有點收獲,明白了道理之後就好辦了,直接照貓畫虎,我采用的是第二種方法:

1、先創建rootfs這個文件夾,再在這個文件夾下麵分別創建bin、dev、etc、proc、sys等目錄

2、編譯busybox,把生成的bin文件複製到rootfs/bin下

3、新建linuxrc文件,設置權限chmod 777,然後在u-boot傳給內核參數中一定要加上init=/linuxrc

4、在dev目錄下,加設備節點,不然會沒有輸出信息哦!

· 1 mknod -m 666 console c 5 1

· 2 mknod -m 666 null c 1 3

5、在內核make menuconfig上CONFIG_INITRAMFS_SOURCE=“你剛剛構建的文件夾的絕對路徑”


編譯內核,initramfs直接和內核編譯在一起,不用另外分出一個bin文件拷,這比較方便,啟動,在串口調試助手中可看到相關顯示信息:

最後說一下自己的感想,用initramfs根文件係統雖然方便實用,但是有弊端就是它隻讀不可寫,這對開發很不利,領導說會加個spi flash再在裏麵掛載個根文件係統(spi flash能掛jfss2嗎?),那是以後的事了以後再說,目前這種情況以我的技術水平隻能做到這個份上了,加了根文件之後,stm32內部flash還有200多k的存儲空間,應該可以添加些驅動和應用程序,那下麵我的任務是編寫簡單的驅動與應用程序。