1) nhị phân biên dịch được ghi vào prom / flash yes. USB, nối tiếp, i2c, jtag, v.v ... tùy thuộc vào thiết bị như những gì được thiết bị đó hỗ trợ, không liên quan để hiểu quá trình khởi động.
2) Điều này thường không đúng với vi điều khiển, trường hợp sử dụng chính là có các hướng dẫn trong rom / flash và dữ liệu trong ram. Không có vấn đề gì về kiến trúc. đối với một bộ vi điều khiển, máy tính của bạn, máy tính xách tay của bạn, máy chủ của bạn, chương trình được sao chép từ không biến động (đĩa) sang ram sau đó chạy từ đó. Một số bộ vi điều khiển cũng cho phép bạn sử dụng ram, ngay cả những bộ vi xử lý yêu cầu máy gặt mặc dù nó có vẻ vi phạm định nghĩa. Không có gì về máy gặt ngăn bạn ánh xạ ram vào phía hướng dẫn, bạn chỉ cần có một cơ chế để nhận các hướng dẫn ở đó sau khi bật nguồn (vi phạm định nghĩa, nhưng các hệ thống bừa sẽ phải làm điều đó có ích hơn là vi điều khiển).
3) loại.
Mỗi cpu "khởi động" theo cách xác định, theo thiết kế. Cách phổ biến nhất là bảng vectơ trong đó địa chỉ cho các hướng dẫn đầu tiên chạy sau khi bật nguồn nằm trong vectơ thiết lập lại, một địa chỉ mà phần cứng đọc sau đó sử dụng địa chỉ đó để bắt đầu chạy. Một cách chung khác là để bộ xử lý bắt đầu thực thi mà không cần bảng vectơ tại một số địa chỉ nổi tiếng. Đôi khi chip sẽ có "dây đai", một số chân mà bạn có thể buộc cao hoặc thấp trước khi phát hành thiết lập lại, mà logic sử dụng để khởi động các cách khác nhau. Bạn phải tách riêng cpu, lõi bộ xử lý khỏi phần còn lại của hệ thống. Hiểu cách cpu hoạt động, và sau đó hiểu rằng các nhà thiết kế chip / hệ thống có bộ giải mã địa chỉ thiết lập xung quanh bên ngoài cpu để một phần của không gian địa chỉ cpus giao tiếp với đèn flash, và một số với ram và một số có thiết bị ngoại vi (uart, i2c, spi, gpio, v.v.). Bạn có thể lấy lõi cpu đó nếu muốn và bọc nó khác nhau. Đây là những gì bạn nhận được khi bạn mua một cái gì đó cánh tay hoặc mips dựa. cánh tay và mips tạo ra lõi cpu, thứ mà mọi người mua và bọc những thứ của riêng họ, vì nhiều lý do họ không làm cho thứ đó tương thích từ thương hiệu này sang thương hiệu khác. Đó là lý do tại sao hiếm khi có thể hỏi một câu hỏi chung chung khi nói đến bất cứ điều gì bên ngoài cốt lõi.
Một bộ vi điều khiển cố gắng trở thành một hệ thống trên chip, do đó bộ nhớ không bay hơi (flash / rom), dễ bay hơi (sram) và cpu đều nằm trên cùng một chip cùng với hỗn hợp các thiết bị ngoại vi. Nhưng chip được thiết kế bên trong sao cho đèn flash được ánh xạ vào không gian địa chỉ của cpu phù hợp với đặc điểm khởi động của cpu đó. Ví dụ, nếu cpu có vectơ thiết lập lại tại địa chỉ 0xFFFC, thì cần phải có flash / rom đáp ứng địa chỉ đó mà chúng ta có thể lập trình qua 1), cùng với đủ flash / rom trong không gian địa chỉ cho các chương trình hữu ích. Một nhà thiết kế chip có thể chọn có 0x1000 byte flash bắt đầu từ 0xF000 để đáp ứng các yêu cầu đó. Và có lẽ họ đặt một số lượng ram ở một địa chỉ thấp hơn hoặc có thể là 0x0000 và các thiết bị ngoại vi ở đâu đó ở giữa.
Một kiến trúc khác của cpu có thể bắt đầu thực thi tại địa chỉ 0, vì vậy họ sẽ cần làm ngược lại, đặt đèn flash để nó trả lời cho phạm vi địa chỉ quanh 0. nói 0x0000 đến 0x0FFF chẳng hạn. và sau đó đặt một số ram ở nơi khác.
Các nhà thiết kế chip biết cách khởi động cpu và họ đã đặt bộ lưu trữ không bay hơi ở đó (flash / rom). Sau đó, các phần mềm phải viết mã khởi động để phù hợp với hành vi được biết đến của cpu đó. Bạn phải đặt địa chỉ vectơ đặt lại trong vectơ đặt lại và mã khởi động của bạn tại địa chỉ bạn đã xác định trong vectơ đặt lại. Toolchain có thể giúp bạn rất nhiều ở đây. đôi khi, đặc biệt với id và click ides hoặc các hộp cát khác, họ có thể làm hầu hết công việc cho bạn, tất cả những gì bạn làm là gọi apis bằng ngôn ngữ cấp cao (C).
Nhưng, tuy nhiên, chương trình được tải vào flash / rom phải phù hợp với hành vi khởi động cứng của cpu. Trước phần C của chương trình chính của bạn () và nếu bạn sử dụng chính làm điểm vào của bạn, một số việc phải được thực hiện. Lập trình viên AC giả định rằng khi khai báo một biến có giá trị ban đầu, họ hy vọng rằng nó thực sự hoạt động. Chà, các biến, không phải là const, nằm trong ram, nhưng nếu bạn có một biến có giá trị ban đầu thì giá trị ban đầu phải nằm trong ram không bay hơi. Vì vậy, đây là phân đoạn .data và bootstrap C cần sao chép nội dung .data từ flash sang ram (nơi thường được xác định cho bạn bởi toolchain). Các biến toàn cục mà bạn khai báo không có giá trị ban đầu được coi là 0 trước khi chương trình của bạn bắt đầu mặc dù bạn thực sự không nên cho rằng và rất may một số trình biên dịch đang bắt đầu cảnh báo về các biến chưa được khởi tạo. Đây là phân đoạn .bss và các số không bootstrap C trong ram, nội dung, số không, không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C. không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C. không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C.
Các lõi dòng cortex-m từ arm sẽ thực hiện một số điều này cho bạn, con trỏ ngăn xếp nằm trong bảng vectơ, có một vectơ thiết lập lại để chỉ mã được chạy sau khi đặt lại, vì vậy khác với bất cứ điều gì bạn phải làm để tạo bảng vectơ (mà bạn thường sử dụng asm cho dù sao) bạn có thể đi thuần C mà không cần asm. bây giờ bạn không nhận được bản sao .data của mình cũng như .bss của bạn bằng 0 nên bạn phải tự làm điều đó nếu bạn muốn thử đi mà không cần asm trên một cái gì đó dựa trên cortex-m. Tính năng lớn hơn không phải là vectơ thiết lập lại mà là các vectơ ngắt trong đó phần cứng tuân theo quy ước gọi C được đề nghị và bảo toàn các thanh ghi cho bạn, và sử dụng trả về đúng cho vectơ đó, để bạn không phải quấn asm bên phải xung quanh mỗi trình xử lý ( hoặc có các chỉ thị cụ thể cho toolchain cho mục tiêu của bạn để có toolchain bao bọc nó cho bạn).
Ví dụ, các công cụ cụ thể của chip có thể là vi điều khiển thường được sử dụng trong các hệ thống sử dụng pin, do đó công suất thấp nên một số thiết bị bị tắt khi hầu hết các thiết bị ngoại vi bị tắt và bạn phải bật từng hệ thống phụ này để bạn có thể sử dụng chúng . Uarts, gpios, v.v ... Thông thường tốc độ đồng hồ thấp được sử dụng, trực tiếp từ bộ tạo dao động hoặc tinh thể. Và thiết kế hệ thống của bạn có thể cho thấy rằng bạn cần một chiếc đồng hồ nhanh hơn, vì vậy bạn khởi tạo nó. đồng hồ của bạn có thể quá nhanh so với đèn flash hoặc ram, do đó bạn có thể cần phải thay đổi trạng thái chờ trước khi bật đồng hồ. Có thể cần phải thiết lập uart, hoặc usb hoặc các giao diện khác. sau đó ứng dụng của bạn có thể làm việc của nó.
Máy tính để bàn, máy tính xách tay, máy chủ và vi điều khiển không khác nhau về cách chúng khởi động / làm việc. Ngoại trừ việc họ không chủ yếu trên một chip. Chương trình bios thường nằm trên một flash / rom chip riêng biệt từ cpu. Mặc dù gần đây x86 cpus đang thu hút ngày càng nhiều những gì từng là chip hỗ trợ vào cùng một gói (bộ điều khiển pcie, v.v.) nhưng bạn vẫn có hầu hết chip ram và rom, nhưng nó vẫn là một hệ thống và nó vẫn hoạt động chính xác giống nhau ở mức cao. Quá trình khởi động cpu được nhiều người biết đến, các nhà thiết kế bảng đặt flash / rom vào không gian địa chỉ nơi cpu khởi động. chương trình đó (một phần của BIOS trên máy tính x86) thực hiện tất cả những điều được đề cập ở trên, nó khởi động nhiều thiết bị ngoại vi khác nhau, nó khởi tạo bộ phim truyền hình, liệt kê các xe buýt pcie, v.v. Người dùng thường có thể định cấu hình khá dựa trên cài đặt bios hoặc những gì chúng ta thường gọi là cài đặt cmos, bởi vì tại thời điểm đó, đó là những gì công nghệ đã được sử dụng. Không quan trọng, có các cài đặt người dùng mà bạn có thể đi và thay đổi để báo cho mã khởi động bios cách thay đổi những gì nó làm.
những người khác nhau sẽ sử dụng thuật ngữ khác nhau. một con chip khởi động, đó là mã đầu tiên chạy. đôi khi được gọi là bootstrap. một bộ tải khởi động với trình tải từ thường có nghĩa là nếu bạn không làm gì để can thiệp thì đó là một bootstrap đưa bạn từ việc khởi động chung vào một cái gì đó lớn hơn, ứng dụng hoặc hệ điều hành của bạn. nhưng phần trình nạp ngụ ý rằng bạn có thể làm gián đoạn quá trình khởi động và sau đó có thể tải các chương trình thử nghiệm khác. nếu bạn đã từng sử dụng uboot chẳng hạn trên một hệ thống linux được nhúng, bạn có thể nhấn một phím và dừng khởi động bình thường thì bạn có thể tải kernel thử nghiệm vào ram và khởi động nó thay vì flash, hoặc bạn có thể tải xuống các chương trình riêng hoặc bạn có thể tải xuống kernel mới sau đó yêu cầu bootloader ghi nó vào flash để lần sau khi bạn khởi động, nó sẽ chạy các công cụ mới.
Theo như cpu, bộ xử lý lõi, không biết ram từ flash từ các thiết bị ngoại vi. Không có khái niệm về bootloader, hệ điều hành, ứng dụng. Nó chỉ là một chuỗi các hướng dẫn được đưa vào cpu để được thực thi. Đây là các thuật ngữ phần mềm để phân biệt các nhiệm vụ lập trình khác nhau với nhau. Khái niệm phần mềm từ nhau.
Một số bộ vi điều khiển có bộ tải khởi động riêng được cung cấp bởi nhà cung cấp chip trong một đèn flash riêng hoặc vùng flash riêng biệt mà bạn có thể không sửa đổi được. Trong trường hợp này thường có một chốt hoặc bộ ghim (tôi gọi chúng là dây đai) mà nếu bạn buộc chúng cao hoặc thấp trước khi thiết lập lại được phát hành, bạn đang nói với logic và / hoặc bộ tải khởi động đó phải làm gì, ví dụ như một kết hợp dây đeo có thể báo cho chip chạy bộ tải khởi động đó và đợi trên uart để dữ liệu được lập trình vào flash. Đặt dây đai theo cách khác và chương trình của bạn khởi động không phải là bộ tải khởi động của nhà cung cấp chip, cho phép lập trình trường của chip hoặc phục hồi sau sự cố chương trình của bạn. Đôi khi nó chỉ là logic thuần túy cho phép bạn lập trình flash. Điều này là khá phổ biến những ngày này,
Lý do tại sao hầu hết các bộ vi điều khiển có flash nhiều hơn ram là vì trường hợp sử dụng chính là để chạy chương trình trực tiếp từ flash và chỉ có đủ ram để bao gồm stack và các biến. Mặc dù trong một số trường hợp, bạn có thể chạy các chương trình từ ram mà bạn phải biên dịch đúng và lưu trữ trong flash sau đó sao chép trước khi gọi.
BIÊN TẬP
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
không phải
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
Vì vậy, đây là một ví dụ cho cortex-m0, cortex-ms đều hoạt động giống như ví dụ này. Ví dụ, con chip cụ thể này có flash ứng dụng tại địa chỉ 0x00000000 trong không gian địa chỉ nhánh và ram ở 0x20000000.
Cách khởi động cortex-m là từ 32 bit tại địa chỉ 0x0000 là địa chỉ để khởi tạo con trỏ ngăn xếp. Tôi không cần nhiều stack cho ví dụ này vì vậy 0x20001000 sẽ đủ, rõ ràng phải có ram bên dưới địa chỉ đó (cách cánh tay đẩy, nó sẽ trừ trước rồi đẩy vì vậy nếu bạn đặt 0x20001000 thì mục đầu tiên trên stack là địa chỉ 0x2000FFFC bạn không phải sử dụng 0x2000FFFC). Từ 32 bit tại địa chỉ 0x0004 là địa chỉ của trình xử lý đặt lại, về cơ bản là mã đầu tiên chạy sau khi đặt lại. Sau đó, có nhiều trình xử lý sự kiện và gián đoạn cụ thể hơn cho lõi và chip vỏ não đó, có thể lên tới 128 hoặc 256, nếu bạn không sử dụng chúng thì bạn không cần phải thiết lập bảng cho chúng, tôi đã ném một vài cái để trình diễn mục đích.
Tôi không cần phải xử lý .data hay .bss trong ví dụ này vì tôi biết đã không có gì trong các phân đoạn đó bằng cách xem mã. Nếu có tôi sẽ giải quyết nó, và sẽ trong một giây.
Vì vậy, ngăn xếp được thiết lập, kiểm tra, .data đã chăm sóc, kiểm tra, .bss, kiểm tra, vì vậy công cụ bootstrap C đã hoàn tất, có thể phân nhánh đến chức năng nhập cho C. Bởi vì một số trình biên dịch sẽ thêm rác nếu họ thấy chức năng main () và trên đường tới main, tôi không sử dụng tên chính xác đó, tôi đã sử dụng notmain () ở đây làm điểm vào C của mình. Vì vậy, trình xử lý thiết lập lại gọi notmain () sau đó if / when notmain () trả về nó sẽ bị treo, đó chỉ là một vòng lặp vô hạn, có thể được đặt tên kém.
Tôi tin tưởng chắc chắn vào việc thành thạo các công cụ, nhiều người thì không, nhưng điều bạn sẽ thấy là mỗi nhà phát triển kim loại trần tự làm việc của mình, vì sự tự do gần như hoàn toàn, không bị ràng buộc từ xa như bạn sẽ làm ứng dụng hoặc trang web . Họ lại làm việc của riêng họ. Tôi thích có mã bootstrap và tập lệnh liên kết của riêng tôi. Những người khác dựa vào chuỗi công cụ, hoặc chơi trong hộp cát của nhà cung cấp, nơi phần lớn công việc được thực hiện bởi người khác (và nếu có gì đó phá vỡ bạn đang ở trong một thế giới bị tổn thương, và với những thứ kim loại trần trụi thường xuyên và theo những cách kịch tính).
Vì vậy, lắp ráp, biên dịch và liên kết với các công cụ gnu tôi nhận được:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
Vậy làm thế nào để bộ nạp khởi động biết công cụ ở đâu. Bởi vì trình biên dịch đã làm việc. Trong trường hợp đầu tiên, trình biên dịch đã tạo mã cho flash.s và bằng cách đó để biết các nhãn ở đâu (nhãn chỉ là địa chỉ giống như tên hàm hoặc tên biến, v.v.) vì vậy tôi không phải đếm byte và điền vào vectơ bảng thủ công, tôi đã sử dụng một tên nhãn và trình biên dịch đã làm điều đó cho tôi. Bây giờ bạn hỏi, nếu thiết lập lại là địa chỉ 0x14 tại sao trình biên dịch lại đặt 0x15 trong bảng vectơ. Vâng, đây là một vỏ não và nó khởi động và chỉ chạy ở chế độ ngón tay cái. Với ARM khi bạn phân nhánh đến một địa chỉ nếu phân nhánh sang chế độ ngón tay cái, lsbit cần được đặt, nếu chế độ tay thì đặt lại. Vì vậy, bạn luôn cần thiết lập bit đó. Tôi biết các công cụ và bằng cách đặt .thumb_func trước một nhãn, nếu nhãn đó được sử dụng như trong bảng vectơ hoặc để phân nhánh hoặc bất cứ thứ gì. Các toolchain biết để thiết lập lsbit. Vậy nó có ở đây 0x14 | 1 = 0x15. Tương tự như vậy cho hang. Bây giờ trình phân tách không hiển thị 0x1D cho lệnh gọi notmain () nhưng đừng lo các công cụ đã xây dựng hướng dẫn chính xác.
Bây giờ mã đó không chính xác, các biến cục bộ đó, không được sử dụng, chúng là mã chết. Trình biên dịch thậm chí còn nhận xét về thực tế đó bằng cách nói y được đặt nhưng không được sử dụng.
Lưu ý không gian địa chỉ, tất cả những thứ này bắt đầu tại địa chỉ 0x0000 và đi từ đó để bảng vectơ được đặt đúng, không gian .text hoặc chương trình cũng được đặt đúng cách, làm thế nào tôi có flash.s trước mã của notmain.c biết các công cụ, một lỗi phổ biến là không làm đúng và sụp đổ và đốt cháy mạnh. IMO bạn phải tháo rời để đảm bảo mọi thứ được đặt đúng trước khi bạn khởi động lần đầu tiên, một khi bạn có đồ đạc ở đúng nơi bạn không nhất thiết phải kiểm tra mỗi lần. Chỉ cho các dự án mới hoặc nếu họ treo.
Bây giờ một điều làm một số người ngạc nhiên là không có lý do gì để mong đợi bất kỳ hai trình biên dịch nào tạo ra cùng một đầu ra từ cùng một đầu vào. Hoặc thậm chí cùng một trình biên dịch với các cài đặt khác nhau. Sử dụng clang, trình biên dịch llvm tôi nhận được hai kết quả đầu ra này mà không cần tối ưu hóa
tối ưu hóa / clang
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
không được tối ưu hóa
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
Vì vậy, đó là một lời nói dối mà trình biên dịch đã tối ưu hóa bổ sung, nhưng nó đã phân bổ hai mục trên ngăn xếp cho các biến, vì đây là các biến cục bộ mà chúng nằm trong ram nhưng trên ngăn xếp không ở các địa chỉ cố định, sẽ thấy với các toàn cục thay đổi. Nhưng trình biên dịch nhận ra rằng nó có thể tính toán y trong thời gian biên dịch và không có lý do nào để tính toán nó trong thời gian chạy nên nó chỉ đơn giản đặt 1 trong không gian ngăn xếp được phân bổ cho x và 2 cho không gian ngăn xếp được phân bổ cho y. trình biên dịch "phân bổ" không gian này với các bảng bên trong Tôi khai báo stack plus 0 cho biến y và stack plus 4 cho biến x. trình biên dịch có thể làm bất cứ điều gì nó muốn miễn là mã nó thực hiện phù hợp với tiêu chuẩn C hoặc các triển khai của một lập trình viên C. Không có lý do tại sao trình biên dịch phải để x ở stack + 4 trong suốt thời gian của hàm,
Nếu tôi thêm một hàm giả trong trình biên dịch chương trình
.thumb_func
.globl dummy
dummy:
bx lr
và sau đó gọi nó
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
đầu ra thay đổi
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
bây giờ chúng ta có các hàm lồng nhau, hàm notmain cần giữ nguyên địa chỉ trả về của nó, để nó có thể ghi đè địa chỉ trả về cho cuộc gọi lồng nhau. điều này là do cánh tay sử dụng một thanh ghi để trả về, nếu nó sử dụng ngăn xếp như nói x86 hoặc một số thứ khác ... thì nó vẫn sử dụng ngăn xếp nhưng khác. Bây giờ bạn hỏi tại sao nó đẩy r4? Chà, quy ước gọi cách đây không lâu đã thay đổi để giữ cho ngăn xếp được căn chỉnh trên ranh giới 64 bit (hai từ) thay vì 32 bit, ranh giới một từ. Vì vậy, họ cần phải đẩy một cái gì đó để giữ cho ngăn xếp được căn chỉnh, vì vậy trình biên dịch tự ý chọn r4 vì một số lý do, không quan trọng tại sao. Popping vào r4 sẽ là một lỗi mặc dù theo quy ước gọi cho mục tiêu này, chúng tôi không ghi đè r4 vào một lệnh gọi hàm, chúng tôi có thể ghi đè r0 đến r3. r0 là giá trị trả về. Có vẻ như nó đang thực hiện tối ưu hóa đuôi,
Nhưng chúng ta thấy rằng toán học x và y được tối ưu hóa thành giá trị mã hóa 2 được truyền cho hàm giả (giả được mã hóa cụ thể trong một tệp riêng biệt, trong trường hợp này là asm, để trình biên dịch sẽ không tối ưu hóa hoàn toàn hàm gọi ra, nếu tôi có một hàm giả chỉ đơn giản là trả về C trong notmain.c thì trình tối ưu hóa sẽ loại bỏ lệnh gọi hàm x, y và giả vì tất cả chúng đều là mã chết / vô dụng).
Cũng lưu ý rằng vì mã flash.s có mã lớn hơn không phải là khác và chuỗi công cụ đã đảm nhiệm việc vá tất cả các địa chỉ cho chúng tôi nên chúng tôi không phải thực hiện thủ công.
tiếng kêu không được tối ưu hóa để tham khảo
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
tiếng kêu tối ưu
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
tác giả trình biên dịch đã chọn sử dụng r7 làm biến giả để căn chỉnh ngăn xếp, ngoài ra, nó đang tạo một con trỏ khung bằng r7 mặc dù nó không có gì trong khung stack. về cơ bản hướng dẫn có thể đã được tối ưu hóa ra. nhưng nó đã sử dụng pop để trả về không phải ba hướng dẫn, đó có lẽ là do tôi, tôi cá là tôi có thể khiến gcc làm điều đó với các tùy chọn dòng lệnh bên phải (chỉ định bộ xử lý).
điều này chủ yếu sẽ trả lời phần còn lại của câu hỏi của bạn
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Tôi có toàn cầu bây giờ. vì vậy chúng đi vào .data hoặc .bss nếu chúng không được tối ưu hóa.
trước khi chúng ta nhìn vào đầu ra cuối cùng, hãy nhìn vào đối tượng lặp lại
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
bây giờ có thông tin bị thiếu trong số này nhưng nó đưa ra ý tưởng về những gì đang diễn ra, trình liên kết là thứ lấy các đối tượng và liên kết chúng với nhau với thông tin được cung cấp (trong trường hợp này là flash.ld) cho biết nó ở đâu .text và. dữ liệu và như vậy đi. trình biên dịch không biết những thứ như vậy, nó chỉ có thể tập trung vào mã được trình bày, bất kỳ bên ngoài nào nó phải để lại một lỗ hổng để trình liên kết điền vào kết nối. Bất kỳ dữ liệu nào nó cũng phải để lại một cách để liên kết những thứ đó lại với nhau, vì vậy địa chỉ của mọi thứ đều bằng 0 ở đây chỉ đơn giản là vì trình biên dịch và trình dịch ngược này không biết. có một thông tin khác không được hiển thị ở đây mà trình liên kết sử dụng để đặt mọi thứ. mã ở đây là vị trí đủ độc lập để trình liên kết có thể thực hiện công việc của nó.
sau đó chúng ta sẽ thấy ít nhất một sự phân tách của đầu ra được liên kết
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
trình biên dịch về cơ bản đã yêu cầu hai biến 32 bit trong ram. Một là trong .bss vì tôi đã không khởi tạo nó nên nó được coi là init. cái khác là .data vì tôi đã khởi tạo nó khi khai báo.
Bây giờ vì đây là các biến toàn cục nên người ta cho rằng các hàm khác có thể sửa đổi chúng. trình biên dịch không đưa ra giả định nào khi không thể gọi được vì vậy nó không thể tối ưu hóa với những gì nó có thể nhìn thấy, toán học y = x + 1, do đó nó phải thực hiện thời gian chạy đó. Nó phải đọc từ ram hai biến thêm chúng và lưu lại.
Bây giờ rõ ràng mã này sẽ không hoạt động. Tại sao? bởi vì bootstrap của tôi như được hiển thị ở đây không chuẩn bị ram trước khi gọi là không chính xác, do đó, bất kỳ rác nào có trong 0x20000000 và 0x20000004 khi chip thức dậy là thứ sẽ được sử dụng cho y và x.
Sẽ không thể hiện điều đó ở đây. bạn có thể đọc lan man thậm chí dài hơn của tôi trên .data và .bss và tại sao tôi không bao giờ cần chúng trong mã kim loại trần của mình, nhưng nếu bạn cảm thấy bạn phải và muốn thành thạo các công cụ thay vì hy vọng người khác làm đúng .. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
các tập lệnh liên kết và bootstraps có phần cụ thể của trình biên dịch, vì vậy mọi thứ bạn tìm hiểu về một phiên bản của một trình biên dịch có thể được đưa vào phiên bản tiếp theo hoặc với một số trình biên dịch khác, nhưng một lý do khác khiến tôi không nỗ lực trong việc chuẩn bị .data và .bss chỉ để được lười biếng này:
unsigned int x=1;
Tôi thích làm điều này hơn
unsigned int x;
...
x = 1;
và để trình biên dịch đặt nó trong .text cho tôi. Đôi khi nó tiết kiệm flash theo cách đó đôi khi nó cháy nhiều hơn. Chắc chắn là dễ dàng hơn nhiều để lập trình và chuyển từ phiên bản toolchain hoặc trình biên dịch này sang trình biên dịch khác. Đáng tin cậy hơn nhiều, ít bị lỗi. Đúng, không phù hợp với tiêu chuẩn C.
Bây giờ nếu chúng ta tạo ra những quả cầu tĩnh này thì sao?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
tốt
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
rõ ràng các biến đó không thể được sửa đổi bởi mã khác, vì vậy trình biên dịch bây giờ có thể biên dịch tối ưu hóa mã chết, giống như trước đây.
không tối ưu hóa
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
trình biên dịch này đã sử dụng ngăn xếp cho người địa phương, bây giờ sử dụng ram cho toàn cầu và mã này được viết bị hỏng vì tôi không xử lý .data cũng như .bss đúng cách.
và một điều cuối cùng mà chúng ta không thể nhìn thấy trong quá trình tháo gỡ.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
Tôi đã thay đổi x thành pre-init với 0x12345678. Kịch bản liên kết của tôi (cái này là dành cho gnu ld) có cái này ở bob. điều đó nói với trình liên kết Tôi muốn vị trí cuối cùng nằm trong không gian địa chỉ ted, nhưng lưu nó ở dạng nhị phân trong không gian địa chỉ ted và ai đó sẽ di chuyển nó cho bạn. Và chúng ta có thể thấy điều đó đã xảy ra. đây là định dạng hex intel. và chúng ta có thể thấy 0x12345678
:0400480078563412A0
là trong không gian địa chỉ flash của nhị phân.
đọc cũng cho thấy điều này
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
dòng LOAD có địa chỉ ảo là 0x20000004 và vật lý là 0x48