SP (ngăn xếp) và LR trong ARM là gì?


77

Tôi đang đọc đi đọc lại các định nghĩa và tôi vẫn không hiểu SP và LR trong ARM là gì? Tôi hiểu PC (nó hiển thị địa chỉ của lệnh tiếp theo), SP và LR có thể tương tự, nhưng tôi không hiểu nó là gì. Liệu bạn có thể giúp mình không?

chỉnh sửa: nếu bạn có thể giải thích nó với các ví dụ, nó sẽ là tuyệt vời.

sửa: cuối cùng cũng tìm ra LR dùng để làm gì, vẫn không nhận được SP dùng để làm gì.


3
Ngăn xếp không dành riêng cho ARM, (hầu hết) mọi bộ xử lý và bộ điều khiển đều có ngăn xếp. secure.wikimedia.org/wikipedia/en/wiki/Call_stack
starblue

Liên quan: Liên kết ARM và con trỏ khung . Con trỏ khung fphoạt động với sp. Trong x86 , fpsẽ là bp; nó cũng là một khái niệm phổ biến trong các lời gọi hàm, một thanh ghi để dự trữ biến cục bộ.
tiếng ồn không có nghệ thuật

Câu trả lời:


89

LR là thanh ghi liên kết được sử dụng để giữ địa chỉ trả về cho một lệnh gọi hàm.

SP là con trỏ ngăn xếp. Ngăn xếp thường được sử dụng để chứa các biến "tự động" và ngữ cảnh / tham số trên các lệnh gọi hàm. Về mặt khái niệm, bạn có thể coi "ngăn xếp" là nơi bạn "chất đống" dữ liệu của mình. Bạn tiếp tục "xếp chồng" một phần dữ liệu lên phần kia và con trỏ ngăn xếp cho bạn biết "mức" cao của "chồng" dữ liệu của bạn. Bạn có thể xóa dữ liệu từ "trên cùng" của "ngăn xếp" và làm cho dữ liệu ngắn hơn.

Từ tham chiếu kiến ​​trúc ARM:

SP, Con trỏ ngăn xếp

Thanh ghi R13 được sử dụng như một con trỏ tới ngăn xếp hoạt động.

Trong mã Ngón tay cái, hầu hết các hướng dẫn không thể truy cập SP. Các lệnh duy nhất có thể truy cập SP là các lệnh được thiết kế để sử dụng SP như một con trỏ ngăn xếp. Việc sử dụng SP cho bất kỳ mục đích nào khác ngoài mục đích làm con trỏ ngăn xếp không được dùng nữa. Lưu ý Sử dụng SP cho bất kỳ mục đích nào khác ngoài mục đích làm con trỏ ngăn xếp có thể phá vỡ các yêu cầu của hệ điều hành, trình gỡ lỗi và hệ thống phần mềm khác, khiến chúng hoạt động sai.

LR, Đăng ký liên kết

Thanh ghi R14 được sử dụng để lưu trữ địa chỉ trả về từ một chương trình con. Vào những thời điểm khác, LR có thể được sử dụng cho các mục đích khác.

Khi một lệnh BL hoặc BLX thực hiện một lệnh gọi chương trình con, LR được đặt thành địa chỉ trả về chương trình con. Để thực hiện trả về chương trình con, hãy sao chép LR trở lại bộ đếm chương trình. Điều này thường được thực hiện theo một trong hai cách, sau khi nhập chương trình con với lệnh BL hoặc BLX:

• Quay lại với lệnh BX LR.

• Trên mục nhập chương trình con, lưu trữ LR vào ngăn xếp với một lệnh có dạng: PUSH {, LR} và sử dụng một lệnh phù hợp để trả về: POP {, PC} ...

Liên kết này đưa ra một ví dụ về một chương trình con tầm thường.

Dưới đây là một ví dụ về cách các đăng ký được lưu trên ngăn xếp trước khi gọi và sau đó bật lại để khôi phục nội dung của chúng.


Cảm ơn, cuối cùng tôi đã nhận ra LR dùng để làm gì, mặc dù vẫn chưa thực sự nhận được SP ...
good_evening

"Ngăn xếp" có nghĩa là gì? Đăng ký? Gì? Bạn có thể cho tôi một ví dụ đơn giản về SP được không?
good_evening

1
@hey Ngăn xếp là nơi bạn giữ các biến mà bạn không thể đưa vào sổ đăng ký. Thông thường các biến có một số cục bộ do cách ngăn xếp hoạt động. Bạn có thể đọc thêm về nó tại đây en.wikipedia.org/wiki/Stack_(abstract_data_type) . Ngoài ra bạn đang ở trên STACKoverflow làm sao bạn không biết nó là gì?
Jesus Ramos

@hey Tôi đã thêm một vài câu để thử và cung cấp cho bạn một số trực giác về ngăn xếp là gì.
Guy Sirton

Chỉ muốn nói rằng không may là cả hai liên kết của bạn hiện đã chết.
hak8or

46

SP là thanh ghi ngăn xếp, lối tắt để gõ r13. LR là liên kết đăng ký một phím tắt cho r14. Và PC là bộ đếm chương trình, một phím tắt để gõ r15.

Khi bạn thực hiện một cuộc gọi, được gọi là lệnh liên kết nhánh, bl, địa chỉ trả về được đặt trong r14, thanh ghi liên kết. máy tính bộ đếm chương trình được thay đổi thành địa chỉ mà bạn đang phân nhánh.

Có một vài con trỏ ngăn xếp trong lõi ARM truyền thống (dòng cortex-m là một ngoại lệ) khi bạn gặp sự cố, ví dụ như bạn đang sử dụng ngăn xếp khác với khi chạy ở nền trước, bạn không phải thay đổi mã của mình mà chỉ sử dụng sp hoặc r13 như bình thường, phần cứng đã thực hiện chuyển đổi cho bạn và sử dụng đúng khi nó giải mã các hướng dẫn.

Tập lệnh ARM truyền thống (không phải ngón tay cái) cho phép bạn tự do sử dụng ngăn xếp theo chiều tăng dần từ địa chỉ thấp đến địa chỉ cao hơn hoặc tăng dần từ địa chỉ cao xuống địa chỉ thấp. các trình biên dịch và hầu hết mọi người đặt con trỏ ngăn xếp lên cao và để nó phát triển từ địa chỉ cao xuống địa chỉ thấp hơn. Ví dụ: có thể bạn có ram từ 0x20000000 đến 0x20008000, bạn đặt tập lệnh trình liên kết để xây dựng chương trình của bạn để chạy / sử dụng 0x20000000 và đặt con trỏ ngăn xếp của bạn thành 0x20008000 trong mã khởi động của bạn, ít nhất là con trỏ ngăn xếp hệ thống / người dùng, bạn phải chia bộ nhớ cho các ngăn xếp khác nếu bạn cần / sử dụng chúng.

Ngăn xếp chỉ là bộ nhớ. Bộ xử lý thường có các lệnh đọc / ghi bộ nhớ đặc biệt dựa trên PC và một số hướng dẫn dựa trên ngăn xếp. Các ngăn xếp ở mức tối thiểu thường được đặt tên là đẩy và bật nhưng không nhất thiết phải như vậy (như với hướng dẫn cánh tay truyền thống).

Nếu bạn truy cập http://github.com/lsasim, tôi đã tạo một bộ xử lý dạy học và có một hướng dẫn về hợp ngữ. Ở đâu đó trong đó tôi thảo luận về ngăn xếp. Nó KHÔNG phải là một bộ xử lý nhánh nhưng câu chuyện cũng giống như vậy, nó sẽ dịch trực tiếp những gì bạn đang cố gắng hiểu trên nhánh hoặc hầu hết các bộ xử lý khác.

Ví dụ, bạn có 20 biến bạn cần trong chương trình của mình nhưng chỉ có 16 thanh ghi trừ đi ít nhất ba trong số chúng (sp, lr, pc) có mục đích đặc biệt. Bạn sẽ phải giữ một số biến của mình trong ram. Giả sử rằng r5 giữ một biến mà bạn sử dụng thường xuyên đến mức bạn không muốn giữ nó trong ram, nhưng có một phần mã mà bạn thực sự cần một thanh ghi khác để thực hiện điều gì đó và r5 không được sử dụng, bạn có thể lưu r5 trên ngăn xếp với nỗ lực tối thiểu trong khi bạn sử dụng lại r5 cho việc khác, sau đó, khôi phục nó một cách dễ dàng.

Cú pháp nhánh truyền thống (không phải là tất cả các cách trở lại ban đầu):

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stm là lưu trữ nhiều, bạn có thể lưu nhiều hơn một đăng ký cùng một lúc, tối đa tất cả chúng trong một hướng dẫn.

db có nghĩa là giảm trước, đây là một ngăn xếp di chuyển xuống từ địa chỉ cao đến địa chỉ thấp hơn.

Bạn có thể sử dụng r13 hoặc sp ở đây để chỉ ra con trỏ ngăn xếp. Hướng dẫn cụ thể này không giới hạn đối với các hoạt động ngăn xếp, có thể được sử dụng cho những việc khác.

Các ! có nghĩa là cập nhật thanh ghi r13 với địa chỉ mới sau khi nó hoàn thành, ở đây một lần nữa stm có thể được sử dụng cho các hoạt động không ngăn xếp nên bạn có thể không muốn thay đổi thanh ghi địa chỉ cơ sở, hãy để lại! tắt trong trường hợp đó.

Sau đó, trong dấu ngoặc vuông {} liệt kê các sổ đăng ký bạn muốn lưu, được phân tách bằng dấu phẩy.

ldmia là ngược lại, ldm có nghĩa là tải nhiều. ia có nghĩa là số tăng sau và phần còn lại giống như stm

Vì vậy, nếu con trỏ ngăn xếp của bạn ở 0x20008000 khi bạn nhấn lệnh stmdb thấy có một thanh ghi 32 bit trong danh sách, nó sẽ giảm trước khi sử dụng nó, giá trị trong r13 vì vậy 0x20007FFC thì nó sẽ ghi r5 thành 0x20007FFC trong bộ nhớ và lưu giá trị 0x20007FFC trong r13. Sau đó, giả sử bạn không có lỗi khi bạn truy cập lệnh ldmia r13 có 0x20007FFC trong đó, có một thanh ghi trong danh sách r5. Vì vậy, nó đọc bộ nhớ ở 0x20007FFC đặt giá trị đó vào r5, ia có nghĩa là tăng sau khi 0x20007FFC tăng một kích thước thanh ghi thành 0x20008000 và! nghĩa là ghi số đó vào r13 để hoàn thành hướng dẫn.

Tại sao bạn lại sử dụng ngăn xếp thay vì chỉ một vị trí bộ nhớ cố định? Cái hay của điều trên là r13 có thể ở bất cứ đâu, nó có thể là 0x20007654 khi bạn chạy mã đó hoặc 0x20002000 hoặc bất cứ điều gì và mã vẫn hoạt động, thậm chí tốt hơn nếu bạn sử dụng mã đó trong một vòng lặp hoặc với đệ quy, nó hoạt động và cho mỗi cấp đệ quy bạn đi, bạn lưu một bản sao mới của r5, bạn có thể có 30 bản sao đã lưu tùy thuộc vào vị trí của bạn trong vòng lặp đó. và khi nó mở ra, nó sẽ đặt lại tất cả các bản sao như mong muốn. với một vị trí bộ nhớ cố định duy nhất không hoạt động. Điều này dịch trực tiếp sang mã C làm ví dụ:

void myfun ( void )
{
   int somedata;
}

Trong một chương trình C như vậy, somedata biến nằm trên ngăn xếp, nếu bạn gọi myfun đệ quy, bạn sẽ có nhiều bản sao của giá trị cho somedata tùy thuộc vào độ sâu trong đệ quy. Ngoài ra, vì biến đó chỉ được sử dụng trong hàm và không cần thiết ở nơi khác nên bạn có thể không muốn ghi một lượng bộ nhớ hệ thống cho biến đó trong suốt thời gian của chương trình, bạn chỉ muốn những byte đó khi ở trong hàm đó và giải phóng bộ nhớ đó khi không có trong chức năng đó. đó là những gì một ngăn xếp được sử dụng cho.

Một biến toàn cục sẽ không được tìm thấy trên ngăn xếp.

Quay lại...

Giả sử bạn muốn triển khai và gọi hàm đó, bạn sẽ có một số mã / hàm mà bạn sử dụng khi gọi hàm myfun. Hàm myfun muốn sử dụng r5 và r6 khi nó đang hoạt động trên một thứ gì đó nhưng nó không muốn bỏ rác bất cứ thứ gì mà ai đó gọi là nó đã sử dụng r5 và r6 vì vậy trong suốt thời gian myfun () bạn muốn lưu các thanh ghi đó trên ngăn xếp. Tương tự như vậy nếu bạn nhìn vào lệnh liên kết nhánh (bl) và thanh ghi liên kết lr (r14) chỉ có một thanh ghi liên kết, nếu bạn gọi một hàm từ một hàm, bạn sẽ cần phải lưu đăng ký liên kết trên mỗi cuộc gọi nếu không bạn không thể quay lại. .

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

Vì vậy, hy vọng bạn có thể thấy cả cách sử dụng ngăn xếp và đăng ký liên kết. Các bộ xử lý khác làm những việc tương tự theo một cách khác. ví dụ, một số sẽ đặt giá trị trả về trên ngăn xếp và khi bạn thực hiện hàm trả về, nó sẽ biết vị trí cần trả về bằng cách kéo một giá trị ra khỏi ngăn xếp. Các trình biên dịch C / C ++, v.v. thông thường sẽ có "quy ước gọi" hoặc giao diện ứng dụng (ABI và EABI là tên cho những cái mà ARM đã xác định). nếu mọi hàm tuân theo quy ước gọi, hãy đặt các tham số mà nó đang truyền cho các hàm đang được gọi vào các thanh ghi bên phải hoặc trên ngăn xếp theo quy ước. Và mỗi hàm tuân theo các quy tắc như thanh ghi nào nó không phải bảo toàn nội dung của nó và thanh ghi nào nó phải bảo toàn nội dung của nó thì bạn có thể có hàm gọi hàm gọi hàm và thực hiện đệ quy và tất cả mọi thứ, miễn là ngăn xếp không đi sâu đến mức nó chạy vào bộ nhớ được sử dụng cho các khối cầu và đống, và như vậy, bạn có thể gọi các hàm và trả về từ chúng cả ngày. Việc triển khai myfun ở trên rất giống với những gì bạn sẽ thấy một sản phẩm trình biên dịch.

Giờ đây, ARM có nhiều lõi và một vài tập lệnh, dòng cortex-m hoạt động hơi khác một chút vì không có nhiều chế độ và con trỏ ngăn xếp khác nhau. Và khi thực hiện các hướng dẫn ngón tay cái ở chế độ ngón tay cái, bạn sử dụng các lệnh đẩy và bật lên không cho bạn quyền tự do sử dụng bất kỳ thanh ghi nào như stm, nó chỉ sử dụng r13 (sp) và bạn không thể lưu tất cả các thanh ghi chỉ một tập hợp con cụ thể của chúng. các bộ lắp ráp cánh tay phổ biến cho phép bạn sử dụng

push {r5,r6}
...
pop {r5,r6}

trong mã cánh tay cũng như mã ngón tay cái. Đối với mã cánh tay, nó mã hóa stmdb và ldmia thích hợp. (ở chế độ ngón tay cái, bạn cũng không có lựa chọn khi nào và ở đâu bạn sử dụng db, giảm trước và ia, tăng sau).

Không, bạn hoàn toàn không phải sử dụng các thanh ghi giống nhau và bạn không cần phải ghép nối cùng một số lượng thanh ghi.

push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

giả sử không có sửa đổi con trỏ ngăn xếp nào khác giữa các lệnh đó nếu bạn nhớ sp sẽ được giảm 12 byte cho lần đẩy, giả sử từ 0x1000 thành 0x0FF4, r5 sẽ được ghi thành 0xFF4, r6 thành 0xFF8 và r7 thành 0xFFC ngăn xếp con trỏ sẽ thay đổi thành 0x0FF4. cửa sổ bật lên đầu tiên sẽ nhận giá trị tại 0x0FF4 và đặt giá trị đó vào r2 sau đó đặt giá trị tại 0x0FF8 và đặt giá trị đó vào r3 con trỏ ngăn xếp nhận giá trị 0x0FFC. sau cửa sổ bật lên cuối cùng, sp là 0x0FFC được đọc và giá trị được đặt trong r1, con trỏ ngăn xếp sau đó nhận giá trị 0x1000, nơi nó bắt đầu.

ARM ARM, Hướng dẫn tham khảo kiến ​​trúc ARM (infocenter.arm.com, hướng dẫn tham khảo, tìm một cái cho ARMv5 và tải xuống, đây là ARM ARM truyền thống với ARM và hướng dẫn ngón tay cái) chứa mã giả cho các lệnh ldm và stm ARM cho bức tranh hoàn chỉnh về cách chúng được sử dụng. Tương tự như vậy, toàn bộ cuốn sách nói về cánh tay và cách lập trình nó. Ở phía trước, chương mô hình lập trình sẽ hướng dẫn bạn qua tất cả các thanh ghi trong tất cả các chế độ, v.v.

Nếu bạn đang lập trình một bộ xử lý ARM, bạn nên bắt đầu bằng cách xác định (nhà cung cấp chip nên cho bạn biết, ARM không sản xuất chip mà nó tạo ra các lõi mà các nhà cung cấp chip đưa vào chip của họ) chính xác lõi nào bạn có. Sau đó, truy cập trang web của nhánh và tìm ARM ARM cho họ đó và tìm TRM (hướng dẫn tham khảo kỹ thuật) cho lõi cụ thể bao gồm cả bản sửa đổi nếu nhà cung cấp đã cung cấp điều đó (r2p0 có nghĩa là bản sửa đổi 2.0 (hai điểm 0, 2p0)), thậm chí nếu có phiên bản mới hơn, hãy sử dụng sách hướng dẫn đi kèm với bản mà nhà cung cấp đã sử dụng trong thiết kế của họ. Không phải mọi lõi đều hỗ trợ mọi lệnh hoặc chế độ mà TRM cho bạn biết các chế độ và hướng dẫn được hỗ trợ ARM ARM đưa ra một lớp phủ về các tính năng cho cả dòng bộ xử lý mà lõi đó sử dụng. Lưu ý rằng ARM7TDMI là ARMv4 KHÔNG PHẢI là ARMv7 tương tự như vậy ARM9 không phải là ARMv9. ARMvNUMBER là họ ARM7, ARM11 không có av là tên lõi. Các lõi mới hơn có tên như Cortex và mpcore thay vì ARMNUMBER, điều này làm giảm sự nhầm lẫn. Tất nhiên, họ phải thêm vào sự nhầm lẫn bằng cách tạo ra ARMv7-m (cortex-MNUMBER) và ARMv7-a (Cortex-ANUMBER) là những họ rất khác nhau, một cái dành cho tải nặng, máy tính để bàn, máy tính xách tay, v.v. cái kia là cho bộ vi điều khiển, đồng hồ và đèn nhấp nháy trên máy pha cà phê và những thứ tương tự. google beagleboard (Cortex-A) và bảng khám phá dòng giá trị stm32 (Cortex-M) để cảm nhận sự khác biệt. Hoặc thậm chí là bảng open-rd.org sử dụng nhiều lõi với tốc độ hơn gigahertz hoặc tegra 2 mới hơn từ nvidia, cùng một công cụ siêu tỷ lệ, muti core, multi gigahertz.

xin lỗi vì bài viết rất dài, hy vọng nó hữu ích.


7
Tôi đã bị hấp dẫn bởi dự án github của bạn để học lắp ráp, nhưng có vẻ như dự án của bạn đã biến mất. Bạn có một thay thế cho nó? :)
Dave

1
Tôi tin rằng địa chỉ hiện tại của dự án là github.com/dwelch67/lsasim (có hiệu lực kể từ hôm nay, ngày 7 tháng 9 năm 2020).
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.