1. 程式人生 > >關於start.S中一些常用匯編語言的理解

關於start.S中一些常用匯編語言的理解

終於明白這個LR暫存器了

看下面這個ARM彙編吧

BL  NEXT                                 ;跳轉到子程式

.........                                          ;NEXT處執行

NEXT

..........

MOV  PC,LR                            ;從子程式返回

這裡的BL是跳轉的意思,LR(R14)儲存了返回地址

PC(R15)是當前地址,把LR給PC就是從子程式返回

這裡有一下總結

首先

1.SP(R13) LR(R14)PC(R15)

2.lr(r14)的作用問題,這個lr一般來說有兩個作用: 1》.當使用bl或者blx跳轉到子過程的時候,r14儲存了返回地址,可以在呼叫過程結尾恢復。 2》.異常中斷髮生時,這個異常模式特定的物理R14被設定成該異常模式將要返回的地址。 另外注意pc,在除錯的時候顯示的是當前指令地址,而用mov lr,pc的時候lr儲存的是此指令向後數兩條指令的地址,大家可以試一下用mov pc,pc,結果得到的是跳轉兩條指令,這個原因是由於arm的流水線造成的,預取兩條指令的結果.

3.》我以前看書不懂的地方

子程式返回的三種方法

現在總結如下

1.MOV PC,LR

2.BL LR

3.在子程式入口處使用以下指令將R14存入堆疊

STMFD    SP!,{<Regs>,LR}

對應的,使用以下指令可以完成子程式的返回

LDMFD  SP!,      {<Regs>,LR}

轉載自:http://blog.csdn.net/xgx198831/article/details/8333446

1.1. 彙編學習總結記錄 對於我們之前分析的start.S中,涉及到很多的彙編的語句,其中,可以看出,很多包含了很多種不同的語法,使用慣例等,下面,就對此進行一些總結,借 以實現一定的舉一反三或者說觸類旁通,這樣,可以起到一定的借鑑功能,方便以後看其他類似彙編程式碼, 容易看懂彙編程式碼所要表達的含義。 1.1.1. 彙編中的標號=C中的標號 像前面彙編程式碼中,有很多的,以點開頭,加上一個名字的形式的標號,比如:  

  1. reset:
  2. /*
  3.   * set the cpu to SVC32 mode
  4.   */
  5. mrs r0,cpsr  

複製程式碼中的reset,就是彙編中的標號,相對來說,比較容易理解,就相當於C語言的標號。 比如,C語言中定義一個標號ERR_NODEV:

  1. ERR_NODEV: /* no device error */
  2. ... /* c code here */

複製程式碼然後對應在別處,使用goto去跳轉到這個標號ERR_NODEV:

  1. if (something)
  2. goto ERR_NODEV ;

複製程式碼【總結】 彙編中的標號 = C語言中的標號Label 1.1.2. 彙編中的跳轉指令=C中的goto 對應地,和上面的例子中的C語言中的編號和掉轉到標號的goto類似,彙編中,對於定義了標號,那麼也會有對應的指令,去跳轉到對應的彙編中的標號。 這些跳轉的指令,就是b指令,b是branch的縮寫。 b指令的格式是: b{cond} label 簡單說就是跳轉到label處。 用和上面的例子相關的程式碼來舉例:

  1. .globl _start
  2. _start: b       reset  

複製程式碼就是用b指令跳轉到上面那個reset的標號。 【總結】 彙編中的b跳轉指令 = C語言中的goto 1.1.3. 彙編中的.globl=C語言中的extern 對於上面例子中: .globl _start   中的.global,就是宣告_start為全域性變數/標號,可以供其他原始檔所訪問。 即彙編器,在編譯此彙編程式碼的時候,會將此變數記下來,知道其是個全域性變數,遇到其他檔案是用到此變數的的時候,知道是訪問這個全域性變數的。 因此,從功能上來說,就相當於C語言用extern去生命一個變數,以實現本檔案外部訪問此變數。 【總結】 彙編中的.globl或.global = C語言中的extern 1.1.4. 彙編中用bl指令和mov pc,lr來實現子函式呼叫和返回 和b指令類似的,另外還有一個bl指令,語法是: BL{cond} label 其作用是,除了b指令跳轉到label之外,在跳轉之前,先把下一條指令地址存到lr暫存器中,以方便跳轉到那邊執行完畢後,將lr再賦值給pc,以實現函式返回,繼續執行下面的指令的效果。 用下面這個start.S中的例子來說明:  

  1. bl cpu_init_crit
  2. 。。。
  3. cpu_init_crit:
  4. 。。。
  5. mov pc, lr  

複製程式碼其中,就是先呼叫bl掉轉到對應的標號cpu_init_crit,其實就是相當於一個函數了, 然後在cpu_init_crit部分,執行完畢後,最後呼叫 mov pc, lr,將lr中的值,賦給pc,即實現函式的返回原先 bl cpu_init_crit下面那條程式碼,繼續執行函式。 上面的整個過程,用C語言表示的話,就相當於

  1. 。。。
  2. cpu_init_crit();
  3. 。。。
  4. void cpu_init_crit(void)
  5. {
  6. 。。。
  7. }

複製程式碼 而關於C語言中,函式的跳轉前後所要做的事情,都是C語言編譯器幫我們實現好了,會將此C語言中的函式呼叫,轉化為對應的彙編程式碼的。 其中,此處所說的,函式掉轉前後所要做的事情,就是: 函式跳轉前:要將當前指令的下一條指令的地址,儲存到lr暫存器中。 函式呼叫完畢後:將之前儲存的lr的值給pc,實現函式跳轉回來。繼續執行下一條指令。 而如果你本身自己寫組合語言的話,那麼這些函式跳轉前後要做的事情,都是你程式設計師自己要關心,要實現的事情。 【總結】 彙編中bl + mov pc,lr = C語言中的子函式呼叫和返回 1.1.5. 彙編中的對應位置有儲存值的標號 = C語言中的指標變數 像前文所解析的程式碼中類似於這樣的:

  1. LABEL1:.word Value2

複製程式碼比如:

  1. _TEXT_BASE:
  2. .word TEXT_BASE  

複製程式碼所對應的含義是,有一個標號_TEXT_BASE 而該標號中對應的位置,所存放的是一個word的值,具體的數值是TEXT_BASE,此處的TEXT_BASE是在別處定義的一個巨集,值是0x33D00000。 所以,即為: 有一個標號_TEXT_BASE,其對應的位置中,所存放的是一個word的值,值為TEXT_BASE=0x33D00000。 總的來說,此種用法的含義,如果用C語言來表示,其實更加容易理解: int *_TEXT_BASE = TEXT_BASE = 0x33D00000 即: int *_TEXT_BASE = 0x33D00000 【C語言中如何引用匯編中的標號】 不過,對於這樣的類似於C語言中的指標的彙編中的標號,在C語言中呼叫到的話,卻是這樣引用的:

  1. /* for the following variables, see start.S */
  2. extern ulong _armboot_start; /* code start */
  3. extern ulong _bss_start; /* code + data end == BSS start */
  4. 。。。
  5. IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
  6. 。。。  

複製程式碼而不是我原以為的,直接當做指標來引用該變數的方式:  

  1. *IRQ_STACK_START = *_armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;

複製程式碼 其中,對應的彙編中的程式碼為:  

  1. .globl _armboot_start
  2. _armboot_start:
  3. .word _start  

複製程式碼所以,針對這點,還是需要注意一下的。至少以後如果自己寫程式碼的時候,在C語言中引用匯編中的global的標號的時候,知道是如何引用該變數的。 【總結】 彙編中類似這樣的程式碼: label1: .word value2 就相當於C語言中的: int *label1 = value2 但是在C語言中引用該標號/變數的時候,卻是直接拿來用的,就像這樣: label1 = other_value 其中label1就是個int型的變數。 1.1.6. 彙編中的ldr+標號,來實現C中的函式呼叫 接著上面的內容,繼續解釋,對於彙編中這樣的程式碼: 第一種: ldr pc, 標號1 。。。 標號1:.word 標號2 。。。 標號2: 。。。(具體要執行的程式碼) 或者是, 第二種: ldr pc, 標號1 。。。 標號1:.word XXX(C語言中某個函式的函式名) 的意思就是,將地址為標號1中內容載入到pc中。 而地址為標號1中的內容,就是標號2。 所以上面第一種的意思: 就很容易看出來,就是把標號2這個地址值,給pc,即實現了跳轉到標號2的位置執行程式碼,就相當於呼叫一個函式,該函式名為標號2. 第二種的意思,和上面類似,是將C語言中某個函式的函式名,即某個地址值,給pc,實現呼叫C中對應的那個函式。 兩種做法,其含義用C語言表達,其實很簡單: PC = *(標號1) = 標號2 舉個例子就是: 第一種:

  1. 。。。
  2. ldr pc, _software_interrupt
  3. 。。。
  4. _software_interrupt: .word software_interrupt
  5. 。。。
  6. software_interrupt:
  7. get_bad_stack
  8. bad_save_user_regs
  9. bl  do_software_interrupt

複製程式碼 就是實現了將標號1,_software_interrupt,對應的位置中的值,標號2,software_interrupt,給pc,即實現了將pc掉轉到software_interrupt的位置,即實現了呼叫函式software_interrupt的效果。 第二種:  

  1. ldr pc, _start_armboot
  2. _start_armboot: .word start_armboot  

複製程式碼含義就是,將標號1,_start_armboot,所對應的位置中的值,start_armboot給pc,即實現了呼叫函式start_armboot的目的。 其中,start_armboot是C語言檔案中某個C語言的函式。 【總結】 彙編中,實現函式呼叫的效果,有如下兩種方法: 方法1: ldr pc, 標號1 。。。 標號1:.word 標號2 。。。 標號2: 。。。(具體要執行的程式碼) 方法2: ldr pc, 標號1 。。。 標號1:.word XXX(C語言中某個函式的函式名) 1.1.7. 彙編中設定某個暫存器的值或給某個地址賦值 在彙編程式碼start.S中,看到不止一處, 類似於這樣的程式碼: 形式1:  

  1. # define pWTCON  0x53000000
  2. 。。。
  3. ldr     r0, =pWTCON
  4. mov     r1, #0x0
  5. str     r1, [r0]  

複製程式碼或者: 形式2:  

  1. # define INTSUBMSK 0x4A00001C
  2. 。。。
  3. ldr r1, =0x7fff
  4. ldr r0, =INTSUBMSK
  5. str r1, [r0]  

複製程式碼其含義,都是將某個值,賦給某個地址,此處的地址,是用巨集定義來定義的,對應著某個暫存器的地址。 其中,形式1是直接通過mov指令來將0這個值賦給r1暫存器,和形式2中的通過ldr偽指令來將0x3ff賦給r1暫存器,兩者區別是,前者是因為已經確定所要賦的值0x0是mov的有效運算元,而後者對於0x3ff不確定是否是mov的有效運算元 (如果不是,則該指令無效,編譯的時候,也無法通過編譯,會出現類似於這樣的錯誤:

  1. start.S: Assembler messages:
  2. start.S:149: Error: invalid constant -- `mov r1,#0xFFEFDFFF'
  3. make[1]: *** [start.o] 錯誤 1
  4. make: *** [cpu/arm920t/start.o] 錯誤 2

複製程式碼) 所以才用ldr偽指令,讓編譯器來幫你自動判斷: (1)如果該運算元是mov的有效運算元,那麼ldr偽指令就會被翻譯成對應的mov指令。 舉例說明: 彙編程式碼:  

  1. # define pWTCON  0x53000000
  2. 。。。
  3. ldr     r0, =pWTCON  

複製程式碼被翻譯後的真正的彙編程式碼:  

  1. 33d00068: e3a00453  mov r0, #1392508928 ; 0x53000000  

複製程式碼(2)如果該運算元不是mov的有效運算元,那麼ldr偽指令就會被翻譯成ldr指令。 舉例說明: 彙編程式碼:

  1. ldr r1, =0x7fff  

複製程式碼被翻譯後的真正的彙編程式碼:  

  1. 33d00080: e59f13f8  ldr r1, [pc, #1016] ; 33d00480 <fiq+0x60>
  2. 。。。
  3. 33d00480: 00007fff  .word 0x00007fff  

複製程式碼即把ldr偽指令翻譯成真正的ldr指令,並且另外分配了一個word的地址空間用於存放該數值,然後用ldr指令將對應地址中的值載入,賦值給r1暫存器。 【總結】 彙編中,一個常用的,用來給某個地址賦值的方法,類似如下形式:  

  1. #define 巨集的名字  暫存器地址
  2. 。。。
  3. ldr r1, =要賦的值
  4. ldr r0, =巨集的名字
  5. str r1, [r0]  

複製程式碼 轉載自:http://bbs.chinaunix.net/thread-2312780-1-1.html