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.