精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

riscv64裸機編程實踐與分析

嵌入式IoT ? 來源:嵌入式IoT ? 作者:嵌入式IoT ? 2020-12-31 10:54 ? 次閱讀

riscv64 裸機編程實踐與分析

  • 1.概述

  • 2.最小工程的構成

  • 3. 鏈接腳本

  • 4.可執行的程序源代碼分析

  • 5.編譯與運行

    • 5.1 編譯

    • 5.2 運行

    • 5.3 調試

  • 6.總結

1.概述

任何芯片在啟動之前都需要有一段匯編代碼,從這段匯編代碼上就可以體現一些架構設計的特點。往往做嵌入式底層開發都需要關注這段匯編代碼的含義,這樣在使用的時候才能全面的了解啟動時做了什么事情,在后續的程序中遇到問題也能復盤推演。

本文就針對riscv64的最開始的啟動部分代碼進行分析,從最小的一個裸機代碼開始分析,徹底的弄清楚riscv啟動的流程。

本次使用的環境是riscv64 qemu,而編譯器是通過下面的地址進行下載

https://www.sifive.com/software

2.最小工程的構成

一個最小的工程包含兩個東西:鏈接腳本以及源代碼。

源代碼就是可以讓cpu執行的代碼,通過交叉編譯工具鏈編譯生成可執行的二進制程序。

鏈接腳本文件則可以告訴程序的布局,比如代碼段,函數的入口等等。有了這兩個文件將編譯出來的程序loader到板子上運行即可。

3. 鏈接腳本

下面看一下hello.ld文件。

OUTPUT_ARCH("riscv")
OUTPUT_FORMAT("elf64-littleriscv")
ENTRY(_start)
SECTIONS
{
/*text:testcodesection*/
.=0x80000000;
.text:{*(.text)}
/*data:Initializeddatasegment*/
.gnu_build_id:{*(.note.gnu.build-id)}
.data:{*(.data)}
.rodata:{*(.rodata)}
.sdata:{*(.sdata)}
.debug:{*(.debug)}
.+=0x8000;
stack_top=.;

/*Endofuninitalizeddatasegement*/
_end=.;
}

對于鏈接腳本(linker script),往往都是規定如何把輸入的文件按照特定的地址放到內存中。

其中就上面的腳本而言:

OUTPUT_ARCH("riscv"):表示輸入文件的架構是riscv。

OUTPUT_FORMAT("elf64-littleriscv"):表示elf64小端。一般arm,riscv,x86都是小端,小端是比較主流的。

ENTRY( _start ):表示函數入口是_start

然后開始進行代碼段的布局,起始地址開始處為0x80000000。然后依次放代碼段、數據段、只讀數據段、全局數據段,debug段等等。

這里需要注意:

.+=0x8000;
stack_top=.;

這里說明,棧頂預留了0x8000個字節空間作為程序的棧空間,因為棧是向上增長的,所以這里預留了一些棧空間。

通過反匯編來查看生成程序的布局情況

#riscv64-unknown-elf-objdump-dhello

hello:fileformatelf64-littleriscv


Disassemblyofsection.text:

0000000080000000<_start>:
80000000:f14022f3csrrt0,mhartid
80000004:00029c63bnezt0,8000001c
80000008:00008117auipcsp,0x8
8000000c:04410113addisp,sp,68#8000804c<_end>
80000010:00000517auipca0,0x0
80000014:03450513addia0,a0,52#80000044
80000018:008000efjalra,80000020

000000008000001c:
8000001c:0000006fj8000001c

0000000080000020:
80000020:100102b7luit0,0x10010
80000024:00054303lbut1,0(a0)
80000028:00030c63beqzt1,80000040
8000002c:0002a383lwt2,0(t0)#10010000
80000030:fe03cee3bltzt2,8000002c
80000034:0062a023swt1,0(t0)
80000038:00150513addia0,a0,1
8000003c:fe9ff06fj80000024
80000040:00008067ret

對于qemu來說,sifive_u的起始地址為0x80000000,將代碼段的入口放在此處。

4.可執行的程序源代碼分析

前面已經描述了鏈接腳本的布局,也就是給程序指定了執行的地址,每個函數以及函數入口在什么地址都已經規劃好了,那么具體的入口函數該如何寫呢?

看看hello.s編程代碼:

.align 2
.equ UART_BASE,         0x10010000
.equ UART_REG_TXFIFO,   0

.section .text
.globl _start

_start:
        csrr  t0, mhartid             # read hardware thread id (`hart` stands for `hardware thread`)
        bnez  t0, halt                   # run only on the first hardware thread (hartid == 0), halt all the other threads

        la    sp, stack_top           # setup stack pointer

        la    a0, msg                 # load address of `msg` to a0 argument register
        jal   puts                    # jump to `puts` subroutine, return address is stored in ra regster

halt:   j     halt                    # enter the infinite loop

puts:                                 # `puts` subroutine writes null-terminated string to UART (serial communication port)
                                      # input: a0 register specifies the starting address of a null-terminated string
                                      # clobbers: t0, t1, t2 temporary registers

        li    t0, UART_BASE           # t0 = UART_BASE
1:      lbu   t1, (a0)                # t1 = load unsigned byte from memory address specified by a0 register
        beqz  t1, 3f                  # break the loop, if loaded byte was null

                                      # wait until UART is ready
2:      lw    t2, UART_REG_TXFIFO(t0) # t2 = uart[UART_REG_TXFIFO]
        bltz  t2, 2b                  # t2 becomes positive once UART is ready for transmission
        sw    t1, UART_REG_TXFIFO(t0) # send byte, uart[UART_REG_TXFIFO] = t1

        addi  a0, a0, 1               # increment a0 address by 1 byte
        j     1b

3:      ret

.section .rodata
msg:
     .string "Hello.
"

根據匯編語言的規則

.align2

表示入口程序以2^2也就是4字節對齊。

.equUART_BASE,0x10010000
.equUART_REG_TXFIFO,0

定義了UART的寄存器的基地址。

接著主要從_start:開始分析。

csrrt0,mhartid#readhardwarethreadid(`hart`standsfor`hardwarethread`)
bnezt0,halt#runonlyonthefirsthardwarethread(hartid==0),haltalltheotherthreads

根據riscv的設計,如果一個部件包含一個獨立的取指單元,那么該部件被稱為核心(core)。

一個RiscV兼容的核心能夠通過多線程技術(或者說超線程技術)支持多個RiscV兼容硬件線程(harts),harts這兒就是指硬件線程, hardware thread的意思。

ba4f8054-4ad0-11eb-8b86-12bb97331649.png


上面的就包含一個E51的核和4個U54的核。

而這段匯編就是將其他的核掛起,只運行hartid == 0的核。

緊接著

lasp,stack_top#setupstackpointer

這里將棧指針sp賦值,sp此時指向棧頂。

laa0,msg#loadaddressof`msg`toa0argumentregister
jalputs#jumpto`puts`subroutine,returnaddressisstoredinraregster

對于riscv 架構來說,a0寄存器表示第一個參數賦值,接著跳轉到puts函數中。

此時傳遞過去的參數為a0,也就是

.section.rodata
msg:
.string"Hello.
"

指向一個只讀的字符串結構的數據。

puts的實現

通過匯編來描述一個串口驅動程序的編寫是比較重要的。

puts:#`puts`subroutinewritesnull-terminatedstringtoUART(serialcommunicationport)
#input:a0registerspecifiesthestartingaddressofanull-terminatedstring
#clobbers:t0,t1,t2temporaryregisters

lit0,UART_BASE#t0=UART_BASE
1:lbut1,(a0)#t1=loadunsignedbytefrommemoryaddressspecifiedbya0register
beqzt1,3f#breaktheloop,ifloadedbytewasnull

#waituntilUARTisready
2:lwt2,UART_REG_TXFIFO(t0)#t2=uart[UART_REG_TXFIFO]
bltzt2,2b#t2becomespositiveonceUARTisreadyfortransmission
swt1,UART_REG_TXFIFO(t0)#sendbyte,uart[UART_REG_TXFIFO]=t1

addia0,a0,1#incrementa0addressby1byte
j1b

3:ret

首先剛才通過a0寄存器將參數傳遞過來,然后從1:開始,讀取字符串,beqz t1, 3f表示當t1 == 0時,跳轉到3:之前。此時會跳出2:循環。

2:則是向串口FIFO送數的過程。

到這里一個字符串輸出就可以正常的執行了。

5.編譯與運行

5.1 編譯

上述程序分析完成會,可以將其進行編譯。

riscv64-unknown-elf-gcc-march=rv64g-mabi=lp64-static-mcmodel=medany-fvisibility=hidden-nostdlib-nostartfiles-Thello.ld-Isifive_uhello.s-ohello

上述編譯過程可以生成hello程序。

#readelf-hhello
ELFHeader:
Magic:7f454c46020101000000000000000000
Class:ELF64
Data:2'scomplement,littleendian
Version:1(current)
OS/ABI:UNIX-SystemV
ABIVersion:0
Type:EXEC(Executablefile)
Machine:RISC-V
Version:0x1
Entrypointaddress:0x80000000
Startofprogramheaders:64(bytesintofile)
Startofsectionheaders:4680(bytesintofile)
Flags:0x0
Sizeofthisheader:64(bytes)
Sizeofprogramheaders:56(bytes)
Numberofprogramheaders:1
Sizeofsectionheaders:64(bytes)
Numberofsectionheaders:7
Sectionheaderstringtableindex:6

可以分析一下gcc攜帶的參數。

-march:可以指定編譯出來的架構,比如rv32或者rv64等等。

-static:表示靜態編譯。

-mabi=lp64:數據模型和浮點參數傳遞規則

數據模型:

- int字長 long字長 指針字長
ilp32/ilp32f/ilp32d 32bits 32bits 32bits
lp64/lp64f/lp64d 32bits 64bits 64bits

浮點傳遞規則

- 需要浮點擴展指令? float參數 double參數
ilp32/lp64 不需要 通過整數寄存器(a0-a1)傳遞 通過整數寄存器(a0-a3)傳遞
ilp32f/lp64f 需要F擴展 通過浮點寄存器(fa0-fa1)傳遞 通過整數寄存器(a0-a3)傳遞
ilp32d/lp64d 需要F擴展和D擴展 通過浮點寄存器(fa0-fa1)傳遞 通過浮點寄存器(fa0-fa1)傳遞

-mcmodel=medany:對于-mcmodel=medlow-mcmodel=medany

-mcmodel=medlow

使用 LUI 指令取符號地址的高20位。LUI 配合其它包含低12位立即數的指令后,可以訪問的地址空間是 -2GiB ~ 2GiB。

對于 RV64 而言,能訪問的就是 0x0000000000000000 ~ 0x000000007FFFFFFF,以及 0xFFFFFFFF800000000 ~ 0xFFFFFFFFFFFFFFFF 這兩個區域,前一個區域即 +2GiB 的地址空間,后一個區域即 -2GiB 的地址空間。其它地址空間就訪問不到了。

-mcmodel=medany

使用 AUIPC 指令取符號地址的高20位。AUIPC 配合其它包含低12位立即數的指令后,可以訪問當前 PC 的前后2GiB (PC - 2GiB ~ PC + 2GiB)的地址空間。

對于RV64,取決于當前 PC 值,能訪問到是 PC - 2GiB 到 PC + 2GiB 這個地址空間。假設當前 PC 是 0x1000000000000000,那么能訪問的地址范圍是 0x0000000080000000 ~ 0x100000007FFFFFFF。假設當前 PC 是 0xA000000000000000,那么能訪問的地址范圍是0x9000000080000000~0xA00000007FFFFFFF。

-fvisibility=hidden:動態庫部分需要對外顯示的函數接口顯示出來。

-nostdlib:不連接系統標準啟動文件和標準庫文件,只把指定的文件傳遞給連接器

-nostartfiles:不帶main函數的入口程序。

-Thello.ld:加載鏈接地址。

5.2 運行

輸入下面的命令即可看到Hello.字符串輸出。

#qemu-system-riscv64-nographic-machinesifive_u-biosnone-kernelhello
Hello.

5.3 調試

調試過程比較只需在運行的后面加-s -S,即

qemu-system-riscv64-nographic-machinesifive_u-biosnone-kernelhello-s-S

另外再開一個終端輸入

riscv64-unknown-elf-gdbhello

接著輸入target remote localhost:1234即可。

通過b _start打斷點,并且通過si進行單步跳轉可實現程序的單步運行。

6.總結

riscv64最小裸機程序的運行很好理解,主要梳理清楚其啟動地址與鏈接文件即可。還有就是注意gcc的編譯參數,這些對于riscv的啟動來說也是非常關鍵的部分。

責任編輯:xj

原文標題:riscv64 裸機編程實踐與分析

文章出處:【微信公眾號:嵌入式IoT】歡迎添加關注!文章轉載請注明出處。


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 編程
    +關注

    關注

    88

    文章

    3521

    瀏覽量

    93273
  • RISC
    +關注

    關注

    6

    文章

    460

    瀏覽量

    83566

原文標題:riscv64 裸機編程實踐與分析

文章出處:【微信號:Embeded_IoT,微信公眾號:嵌入式IoT】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    freertos和裸機有什么區別

    FreeRTOS 和裸機編程是兩種不同的嵌入式系統開發方法,它們在設計理念、資源使用、功能實現等方面有著顯著的差異。 1. 基本概念 1.1 FreeRTOS FreeRTOS 是一個小型的、可裁剪
    的頭像 發表于 09-02 14:13 ?276次閱讀

    美國硅谷raksmart站群裸機服務器租用費用分析

    RAKsmart是一家提供數據中心服務的公司,其在美國硅谷擁有數據中心,并提供包括站群裸機服務器在內的多種服務器租賃服務。站群服務器通常用于托管大量網站或應用程序,因此對硬件性能和網絡穩定性有著較高的要求。接下來我們將對RAKsmart硅谷站群裸機服務器的租用費用進行
    的頭像 發表于 08-29 10:05 ?104次閱讀

    東京裸機云多IP服務器全面分析

    東京裸機云多IP服務器是一種提供多IP地址分配和高性能網絡服務的云計算解決方案,廣泛應用于需要多IP管理和高穩定性的網絡應用。下面將從幾個方面具體介紹東京裸機云多IP服務器,rak部落為您整理發布東京裸機云多IP服務器的全面
    的頭像 發表于 07-22 09:49 ?210次閱讀

    在ubuntu 24.04下嘗試使用riscv64-linux-musleabi_for_x86_64-pc-linux-gnu工具鏈編譯cv1800大核出現報錯的原因?

    在ubuntu 24.04下嘗試使用riscv64-linux-musleabi_for_x86_64-pc-linux-gnu工具鏈編譯cv1800大核,結果出現如下報錯: /home
    發表于 07-16 08:20

    洛杉磯裸機云大寬帶服務器的特性和優勢

    洛杉磯裸機云大寬帶服務器是結合了物理服務器性能和云服務靈活性的高性能計算服務,為用戶提供高效、安全的計算和存儲能力。在了解如何使用洛杉磯裸機云大寬帶服務器之前,需要了解其基本特性和優勢。以下是對洛杉磯裸機云大寬帶服務器的具體
    的頭像 發表于 07-08 10:11 ?140次閱讀

    振弦采集儀的工程安全監測實踐與案例分析

    振弦采集儀的工程安全監測實踐與案例分析 振弦采集儀是一種常用的工程安全監測儀器,通過測量被監測結構的振動頻率與振型,可以實時監測結構的安全狀況。本文將結合實踐經驗和案例分析,探討振弦采
    的頭像 發表于 07-01 11:01 ?125次閱讀
    振弦采集儀的工程安全監測<b class='flag-5'>實踐</b>與案例<b class='flag-5'>分析</b>

    使用msys2 mingw64編譯nuclei openocd源碼出錯的原因?

    :msys64homeAdministratorbuildnuclei-riscv-openocdbuild/../src/jtag/drivers/mpsse.c:358:(.text+0xc71): undefined reference
    發表于 05-29 07:52

    谷歌安卓系統即將取消對RISC-V架構的支持

    負責安卓Linux核心分支開發的谷歌高級工程師向AOSP提交了一系列補丁,其中顯示“已去除ACK對riscv64的支持”。這些補丁詳細描述指出“對risc64 GKI內核的支持已停止”。
    的頭像 發表于 04-30 15:40 ?1304次閱讀

    國產riscv芯片大匯總?

    請問有統計國產的riscv芯片的嗎?能匯總一下嗎?
    發表于 04-27 11:53

    RISCV soft JTAG調試_v1.2

    因為目前軟件的限制,RISCV的邏輯不能同時共用JTAG,所以如果想要同時去調試邏輯和RISCV的話,可以通過RISCV的soft Jtag來實現。soft Jtag就是通過GPIO來實現的軟件
    的頭像 發表于 04-23 08:38 ?743次閱讀

    全志D1s開發板裸機開發之壞境搭建

    環境搭建 開發板介紹 張天飛老師編寫的《RISC-V體系結構編程實踐》,里面的源碼是基于 QEMU 模擬器的,可以認為它是一款虛擬的開發板。如果需要在真實開發板上學習,可以使用百問網
    發表于 03-06 13:54

    RISCV soft JTAG調試_v1.1

    因為目前軟件的限制,RISCV的邏輯不能同時共用JTAG,所以如果想要同時去調試邏輯和RISCV的話,可以通過RISCV的soft Jtag來實現。soft Jtag就是通過GPIO來實現的軟件
    的頭像 發表于 02-23 16:16 ?464次閱讀
    <b class='flag-5'>RISCV</b> soft JTAG調試_v1.1

    【昉·星光 2 高性能RISC-V單板計算機體驗】為 Ubuntu 安裝 Docker 及常用軟件

    : 獲取鏡像 通常來說,RISC-V 架構的開發板不能使用基于其他架構開發的鏡像,下面是一些基于 RISC-V 鏡像的合集:https://hub.docker.com/u/riscv64/ 安裝其他常用軟件 sudo apt install vim htop net-tools 根據所需即可。
    發表于 02-21 17:54

    Linux裸機點燈

    Linux裸機
    怎么啦
    發布于 :2023年10月27日 08:47:24

    RT-Smart riscv64匯編注釋

    以rt-smart在全志D1上的代碼為例,主要注釋了rt-smart在riscv64上的系統初始化和異常處理的代碼
    的頭像 發表于 10-12 17:26 ?508次閱讀
    RT-Smart <b class='flag-5'>riscv64</b>匯編注釋