C51填坑記:中斷處理導致主程序函數參數改變

1.現象

平臺:keil c51,中穎SH79F7019A

現象:在增加了一箇中斷處理邏輯後,發現主程序異常,斷點調試發現某個函數的參數被改變了,程序使用了錯誤的數據導致邏輯出錯。

2.排查

初步分析,可能原因如下:

1.參數寄存器(R0-R7)的值,被中斷函數改變。

2.堆棧溢出。

2.1參數寄存器

首先排查參數寄存器(中斷裏面調用了函數,有參數傳遞)。通過仿真器觀察中斷函數彙編代碼,發現在進入中斷之前是對R0-R7進行了壓棧操作的。進一步將中斷有關的函數全部用“using”關鍵字知道不同的寄存器組,發現問題沒有得到改善。到此可以排除參數寄存器原因。

2.2堆棧溢出

排除寄存器之後,進一步想到可能是堆棧溢出,導致數據錯亂。由於51單片機棧空間是有限的(只能使用片內RAM,編譯器將片內變量分配好之後,將剩餘空間作爲棧空間使用,參考:https://wenku.baidu.com/view/7659066ffbd6195f312b3169a45177232e60e472.html)。如果函數調用層數過多,加之中斷本身需要保護現場,可能導致棧溢出。

於是將斷點設置在最底層的函數,觀察堆棧寄存器“SP”的值。結果發現被沒有發生溢出。因此可以排除。

2.3回到原點

排除了上面的原因之後,只能回到最開始出異常的地方繼續跟蹤調試。發現該函數參數並不是直接通過寄存器傳遞,而是保存內存裏面(圖1),後續使用的時候,去裏面讀取。

圖1

於是查看map文件(根據所選編譯器不同可能是m51文件,這個文件有詳細內存分配信息)發現在很多變量都放在了這個地址(這是C51編譯器的特性之一,C51的局部變量在編譯的時候就分配好了,編譯器將他認爲可以複用的變量放在同一地址,已到達節省空間的目的),其中就有在中斷中調用的函數的形參(圖2)。

圖2

當中斷運行的時候,這個內存地址的數據被修改了,而這個地址內容是不會被壓棧的(只會對關鍵的寄存器值進行壓棧)。這就導致回到主程序後,繼續使用這個地址的函數取到錯誤的數據。從而出現錯誤。

3.C51參數傳遞

3.1參數

順帶提一下C51的參數傳遞規則。在C51中函數傳遞參數的總體思想是:儘量使用寄存器傳遞參數,萬不得已時採樣編譯器分配的固定位置傳遞,對於可重入函數(使用“reentrant”關鍵字修飾的函數)將會使用模擬堆棧傳遞(這種方式會消耗更多的RAM)。

寄存器參數傳遞:寄存器參數傳遞指參數通過寄存器R1~R7來傳遞的,這種形式可產生高效的代碼,利用51單片機的工作寄存器最多傳遞3個參數

 

固定位置傳遞:如果不適用寄存器來傳遞參數、或者參數過多、或者無法用寄存器傳遞的數據類型(bit類型),則需要通過編譯器分配的固定位置進行傳遞。此外,如果第一個參數時bit類型,那麼所有參數將使用固定位置傳遞。

3.2返回值

C51返回值最多隻有一個。傳遞方式如下:

4.深入挖掘 

要解決這個問題,需要了解編譯器分配變量的機制。前面提到的不同變量使用相同的地址,主要時由於編譯器使用了數據覆蓋(Overlay)技術(參考:http://www.keil.com/support/man/docs/lx51/lx51_overlaying.htm)。編譯器將函數參數和局部比變量保存在固定的內存區域。通過分析程序結構,編譯器生成函數調用樹。然後根據調用樹決定那些變量可被覆蓋。不同調用樹使用的內存區域時分開的,不會重疊。程序編譯完成後,完整的調用樹可以在“Objects”目錄下的“map”文件查看(如果時BL51編譯器,則是“m51”)。該文件是否生成需要在編譯選項“Listing”裏面設置。

一般情況下,編譯器可以自動處理調用關係。但是以下情況則需要特殊處理:

1.使用了間接調用(函數指針)。

2.使用了函數指針數組。

3.遞歸調用

4.重入函數(一個函數會被多個任務調用,即數據兩個或兩個以上的調用樹)

對於這些情況,需要手動配置調整調用關係。

本文所遇到的問題,就是屬於第1、2中情況。在中斷函數中使用了函數指針進行間接調用,導致編譯器無法正確解析調用關係,從而導致兩個函數參數地址重疊。

4.1 調整調用樹

編譯器提供了“OVERLAY”指令來手動調整函數調用關係(參考:http://www.keil.com/support/man/docs/lx51/lx51_overlay.htm)。語法如下:

1.增加新的跟節點(“sfname”表示函數名稱,可在map文件查看)

OVERLAY (* ! sfname)

2.移除根節點

OVERLAY (sfname ! *)

3.移除調用關係(“caller”表示調用者,“collee”表示被調用者)

OVERLAY (sfname-caller ~ sfname-callee)
OVERLAY (sfname-caller ~ (sfname-callee, sfname-callee))

4.添加調用關係(“caller”表示調用者,“collee”表示被調用者)

OVERLAY (sfname-caller ! sfname-callee)
OVERLAY (sfname-caller ! (sfname-callee, sfname-callee))

在keil中調整調用關係方法如下:

打開設置選項,選擇“LX51 Misc”,然後在“Overlay”裏面輸入規則即可,也可以勾選“use linker control file”。這個編譯器控制文件其實就是個後綴爲“.lin”的文本文件。可以點擊“create”創建一個,然後添加規則即可。 

5 總結

這個問題原因是在使用了函數指針導致編譯器不能正確的解析調用關係,從而出現參數地址重疊。

使用中斷的時候,需要注意的問題:

1.中斷和主程序屬於不同的調用樹,儘量不要調用同一個函數,如果不可避免,需要聲明爲重入函數。

2.如果使用了函數指針,需要手動維護函數調用關係。

6.參考鏈接:

C51中函數參數傳遞個數:http://www.51hei.com/bbs/dpj-31890-1.html

C51單片機堆棧深入剖析:https://wenku.baidu.com/view/7659066ffbd6195f312b3169a45177232e60e472.html

Keil C 堆棧溢出【轉至:半島漁翁的博客】:https://blog.csdn.net/zhang2005/article/details/46341121

C51 Product Manuals: http://www.keil.com/support/man_c51.htm

LX51 Data Overlaying: http://www.keil.com/support/man/docs/lx51/lx51_overlaying.htm

OVERLAY Linker Directive: http://www.keil.com/support/man/docs/lx51/lx51_overlay.htm

The Call Tree: http://www.keil.com/support/man/docs/lx51/lx51_ol_calltree.htm

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章