22、64位ARM汇编语言编程的安全与指令集详解

64位ARM汇编语言编程的安全与指令集详解

1. 防止栈上代码运行

在早期,栈溢出攻击通常是将黑客的汇编语言程序作为缓冲区的一部分复制,然后覆盖函数的返回地址来执行该代码。ARM CPU的硬件安全机制会将内存页标记为可读、可写和可执行。为防止栈上代码运行,Linux移除了允许栈上代码执行的标志位,使栈仅具备读写权限。

在默认情况下,栈上代码执行功能是关闭的。如果要开启,需要添加大量额外的编译和链接开关。不过,这并不意味着栈上代码执行完全不可能,只是难度大大增加,黑客需要额外的手段来禁用这一特性。此外,使用的共享库可能会在你不知情的情况下禁用该特性。

2. 缓冲区溢出缓解技术的权衡

在设计API时,需要格外小心以防止安全漏洞。应优先使用能防止缓冲区溢出的例程,例如使用 strncpy 而非 strcpy ,并在源代码控制系统的代码提交过程中添加检查。然而,这些方法仍存在权衡和弱点。

要警惕数据泄露问题。如果在错误消息中包含内存地址,黑客可以利用它来确定PIE偏移量。像Spectre和Meltdown这样的CPU漏洞,展示了如何访问CPU缓存中的内存位。虽然黑客不太可能通过这种方式找到密码,但很可能会找到内存地址或栈金丝雀。

如果开启并使用所有可用的缓冲区溢出保护技术和工具,代码的运行速度可能会降低多达50%。在某些应用程序或部分应用中,这可能是可以接受的,但对于需要高性能的部分,就需要权衡。

对于需要高度优化的代码段,要确保在其外部有一个层或模块来清理并确保传递给优化例程的数据的正确性。同时,要保证数据检查不能被绕过,并且数据能通过优化例程中的所有假设。代码和安全审查有助于发现潜在问题,审查人员需要具备安全和黑客攻击方面的专业知识。

需要注意的是,将数据检查代码放在用户界面模块通常是错误的。以Web应用程序为例,UI通常用JavaScript编写并在浏览器中运行。由于JavaScript是解释型语言,黑客可以修改JavaScript来绕过错误检查,甚至可以完全绕过JavaScript直接向Web服务器发送恶意消息。所有客户端/服务器应用程序都存在类似问题,服务器必须自行验证数据,不能依赖UI层。

另外,Linux的PIE等功能存在一个弱点:如果链接了任何禁用PIE的共享库,整个应用程序的PIE功能都会被禁用。因此,要确保最终的可执行文件仍然启用了PIE,否则需要找到并替换有问题的库。禁用栈执行也是同样的道理,没有理由不使用PIE或防止栈执行,因为它们不会降低应用程序的性能。

同样,即使代码中启用了栈金丝雀,但使用的共享库可能没有以该选项编译。这样一来,虽然代码本身受到保护,但如果黑客在共享库的例程中发现缓冲区溢出,仍可能进行攻击。由于栈金丝雀的使用成本较高,程序员通常会谨慎使用或根本不使用。

黑客很聪明,会寻找应用程序安全防护中的小漏洞进行攻击。他们很有耐心,如果发现一个漏洞不足以利用,会继续寻找。通过组合多个信息和漏洞,他们就能破解程序的安全防护。

3. ARM 64位核心指令集

ARM 64位指令集分为两部分:核心指令集和NEON及FPU指令。每个指令都有简要描述,指令后的 {S} 表示可以选择设置条件标志, 表示该指令是别名。以下是部分核心指令的介绍:
| 指令 | 描述 |
| ---- | ---- |
| ADC{S} | 带进位加法 |
| ADD{S} | 加法 |
| ADDG | 带标签加法 |
| ADR | 形成PC相对地址 |
| ADRP | 形成到4KB页面的PC相对地址 |
| AND{S} | 按位与 |
| ASR† | 算术右移 |
| ASRV | 可变算术右移 |
| AT† | 地址转换 |
| AUTDA, AUTDZA | 使用密钥A对数据地址进行认证 |
| AUTDB, AUTDZB | 使用密钥B对数据地址进行认证 |
| AUTIA, AUTIA1716 | 使用密钥A对指令地址进行认证 |
| AUTIASP, AUTIAZ | 使用密钥A对指令地址进行认证 |
| AUTIZA | 使用密钥A对指令地址进行认证 |
| AUTIB, AUTIB1716 | 使用密钥B对指令地址进行认证 |
| AUTIBSP, AUTIBZ | 使用密钥B对指令地址进行认证 |
| AUTIZB | 使用密钥B对指令地址进行认证 |
| AXFlag | 转换浮点条件标志 |
| B | 分支 |
| B.cond | 条件分支 |
| BFC† | 位域清除 |
| BFI† | 位域插入 |
| BFM | 位域移动 |
| BFXIL† | 低位位域提取并插入 |
| BIC{S} | 按位清位 |
| BL | 带链接分支 |
| BLR | 带链接到寄存器分支 |
| BLRAA, BLRAAZ | 带指针认证的带链接到寄存器分支 |
| BLRAB, BLRABZ | 带指针认证的带链接到寄存器分支 |
| BR | 到寄存器分支 |
| BRAA, BRAAZ | 带指针认证的到寄存器分支 |
| BRAB, BRABZ | 带指针认证的到寄存器分支 |
| BRK | 断点指令 |
| BTI | 分支目标识别 |
| CAS, CASA | 比较并交换内存中的字或双字 |
| CASAL, CASL | 比较并交换内存中的字或双字 |
| CASB, CASAB | 比较并交换内存中的字节 |
| CASALB, CASLB | 比较并交换内存中的字节 |
| CASH, CASAH | 比较并交换内存中的半字 |
| CASALH, CASLH | 比较并交换内存中的半字 |
| CASP, CASPA | 比较并交换内存中的字对或双字对 |
| CASPAL, CASPL | 比较并交换内存中的字对或双字对 |
| CBNZ | 非零比较并分支 |
| CBZ | 零比较并分支 |
| CCMN | 条件负比较 |
| CCMP | 条件比较 |
| CFINV | 反转进位标志 |
| CFP† | 按上下文限制控制流预测 |
| CINC† | 条件递增 |
| CINV† | 条件反转 |
| CLREX | 清除独占 |
| CLS | 计算前导符号位 |
| CLZ | 计算前导零 |
| CMN† | 负比较 |
| CMP† | 比较 |
| CMPP† | 带标签比较 |
| CNEG† | 条件取负 |
| CPP† | 按上下文限制缓存预取预测 |
| CRC32B, CRC32H | CRC32校验和 |
| CRC32W, CRC32X | CRC32校验和 |
| CRC32CB | CRC32C校验和 |
| CRC32CH | CRC32C校验和 |
| CRC32CW | CRC32C校验和 |
| CRC32CX | CRC32C校验和 |
| CSDB | 推测数据消费屏障 |
| CSEL | 条件选择 |
| CSET† | 条件设置 |
| CSETM† | 条件设置掩码 |
| CSINC | 条件选择递增 |
| CSINV | 条件选择反转 |
| CSNEG | 条件选择取负 |
| DC† | 数据缓存操作 |
| DCPS1 | 调试将PE状态更改为EL1 |
| DCPS2 | 调试将PE状态更改为EL2 |
| DCPS3 | 调试将PE状态更改为EL3 |
| DMB | 数据内存屏障 |
| DRPS | 调试恢复进程状态 |
| DSB | 数据同步屏障 |
| DVP† | 按上下文限制数据值预测 |
| EON | 按位异或非 |
| EOR | 按位异或 |
| ERET | 异常返回 |
| ERETAA, ERETAB | 带指针认证的异常返回 |
| ESB | 错误同步屏障 |
| EXTR | 提取寄存器 |
| GMI | 标签掩码插入 |
| HINT | 提示指令 |
| HLT | 暂停指令 |
| HVC | 管理程序调用 |
| IC† | 指令缓存操作 |
| IRG | 插入随机标签 |
| ISB | 指令同步屏障 |
| LDADD, LDADDA | 内存中字或双字的原子加法 |
| LDADDAL, LDADDL | 内存中字或双字的原子加法 |
| LDADDB, LDADDAB | 内存中字节的原子加法 |
| LDADDALB | 内存中字节的原子加法 |
| LDADDLB | 内存中字节的原子加法 |
| LDADDH | 内存中半字的原子加法 |
| LDADDAH | 内存中半字的原子加法 |
| LDADDALH | 内存中半字的原子加法 |
| LDADDLH | 内存中半字的原子加法 |
| LDAPR | 加载获取RCpc寄存器 |
| LDAPRB | 加载获取RCpc寄存器字节 |
| LDAPRH | 加载获取RCpc寄存器半字 |
| LDAPUR | 加载获取RCpc寄存器(未缩放) |
| LDAPURB | 加载获取RCpc寄存器字节(未缩放) |
| LDAPURH | 加载获取RCpc寄存器半字(未缩放) |
| LDAPURSB | 加载获取RCpc寄存器有符号字节(未缩放) |
| LDAPURSH | 加载获取RCpc寄存器有符号半字(未缩放) |
| LDAPURSW | 加载获取RCpc寄存器有符号字(未缩放) |
| LDAR | 加载获取寄存器 |
| LDARB | 加载获取寄存器字节 |
| LDARH | 加载获取寄存器半字 |
| LDAXP | 加载获取独占寄存器对 |
| LDAXR | 加载获取独占寄存器 |
| LDAXRB | 加载获取独占寄存器字节 |
| LDAXRH | 加载获取独占寄存器半字 |
| LDCLR, LDCLRA | 内存中字或双字的原子位清除 |
| LDCLRAL, LDCLRL | 内存中字或双字的原子位清除 |
| LDCLRB, LDCLRAB | 内存中字节的原子位清除 |
| LDCLRALB | 内存中字节的原子位清除 |
| LDCLRLB | 内存中字节的原子位清除 |
| LDCLRH, LDCLRAH | 内存中半字的原子位清除 |
| LDCLRALH | 内存中半字的原子位清除 |
| LDCLRLH | 内存中半字的原子位清除 |
| LDEOR, LDEORA | 内存中字或双字的原子异或 |
| LDEORAL, LDEORL | 内存中字或双字的原子异或 |
| LDEORB, LDEORAB | 内存中字节的原子异或 |
| LDEORALB | 内存中字节的原子异或 |
| LDEORLB | 内存中字节的原子异或 |
| LDEORH, LDEORAH | 内存中半字的原子异或 |
| LDEORALH | 内存中半字的原子异或 |
| LDEORLH | 内存中半字的原子异或 |
| LDG | 加载分配标签 |
| LDGV | 加载分配标签 |
| LDLAR | 加载LOAcquire寄存器 |
| LDLARB | 加载LOAcquire寄存器字节 |
| LDLARH | 加载LOAcquire寄存器半字 |
| LDNP | 带非临时提示的加载寄存器对 |
| LDP | 加载寄存器对 |
| LDPSW | 加载有符号字寄存器对 |
| LDR | 加载寄存器 |
| LDRAA, LDRAB | 带指针认证的加载寄存器 |
| LDRB | 加载寄存器字节 |
| LDRH | 加载寄存器半字 |
| LDRSB | 加载有符号字节寄存器 |
| LDRSH | 加载有符号半字寄存器 |
| LDRSW | 加载有符号字寄存器 |
| LDSET, LDSETA | 内存中字或双字的原子位设置 |
| LDSETAL, LDSETL | 内存中字或双字的原子位设置 |
| LDSETB, LDSETAB | 内存中字节的原子位设置 |
| LDSETALB | 内存中字节的原子位设置 |
| LDSETLB | 内存中字节的原子位设置 |
| LDSETH, LDSETAH | 内存中半字的原子位设置 |
| LDSETALH | 内存中半字的原子位设置 |
| LDSETLH | 内存中半字的原子位设置 |
| LDSMAX | 内存中字或双字的原子有符号最大值 |
| LDSMAXA | 内存中字或双字的原子有符号最大值 |
| LDSMAXAL | 内存中字或双字的原子有符号最大值 |
| LDSMAXL | 内存中字或双字的原子有符号最大值 |
| LDSMAXB | 内存中字节的原子有符号最大值 |
| LDSMAXAB | 内存中字节的原子有符号最大值 |
| LDSMAXALB | 内存中字节的原子有符号最大值 |
| LDSMAXLB | 内存中字节的原子有符号最大值 |
| LDSMAXH | 内存中半字的原子有符号最大值 |
| LDSMAXAH | 内存中半字的原子有符号最大值 |
| LDSMAXALH | 内存中半字的原子有符号最大值 |
| LDSMAXLH | 内存中半字的原子有符号最大值 |
| LDSMIN, LDSMINA | 内存中字或双字的原子有符号最小值 |
| LDSMINAL | 内存中字或双字的原子有符号最小值 |
| LDSMINL | 内存中字或双字的原子有符号最小值 |
| LDSMINB | 内存中字节的原子有符号最小值 |
| LDSMINAB | 内存中字节的原子有符号最小值 |
| LDSMINALB | 内存中字节的原子有符号最小值 |
| LDSMINLB | 内存中字节的原子有符号最小值 |
| LDSMINH | 内存中半字的原子有符号最小值 |
| LDSMINAH | 内存中半字的原子有符号最小值 |
| LDSMINALH | 内存中半字的原子有符号最小值 |
| LDSMINLH | 内存中半字的原子有符号最小值 |
| LDTR | (非特权)加载寄存器 |
| LDTRB | (非特权)加载寄存器字节 |
| LDTRH | (非特权)加载寄存器半字 |
| LDTRSB | (非特权)加载有符号字节寄存器 |
| LDTRSH | (非特权)加载有符号半字寄存器 |
| LDTRSW | (非特权)加载有符号字寄存器 |
| LDUMAX | 内存中字或双字的原子无符号最大值 |
| LDUMAXA | 内存中字或双字的原子无符号最大值 |
| LDUMAXAL | 内存中字或双字的原子无符号最大值 |
| LDUMAXL | 内存中字或双字的原子无符号最大值 |
| LDUMAXB | 内存中字节的原子无符号最大值 |
| LDUMAXAB | 内存中字节的原子无符号最大值 |
| LDUMAXALB | 内存中字节的原子无符号最大值 |
| LDUMAXLB | 内存中字节的原子无符号最大值 |
| LDUMAXH | 内存中半字的原子无符号最大值 |
| LDUMAXAH | 内存中半字的原子无符号最大值 |
| LDUMAXALH | 内存中半字的原子无符号最大值 |
| LDUMAXLH | 内存中半字的原子无符号最大值 |
| LDUMIN | 内存中字或双字的原子无符号最小值 |
| LDUMINA | 内存中字或双字的原子无符号最小值 |
| LDUMINAL | 内存中字或双字的原子无符号最小值 |
| LDUMINL | 内存中字或双字的原子无符号最小值 |
| LDUMINB | 内存中字节的原子无符号最小值 |
| LDUMINAB | 内存中字节的原子无符号最小值 |
| LDUMINALB | 内存中字节的原子无符号最小值 |
| LDUMINLB | 内存中字节的原子无符号最小值 |
| LDUMINH | 内存中半字的原子无符号最小值 |
| LDUMINAH | 内存中半字的原子无符号最小值 |
| LDUMINALH | 内存中半字的原子无符号最小值 |
| LDUMINLH | 内存中半字的原子无符号最小值 |
| LDUR | (未缩放)加载寄存器 |
| LDURB | (未缩放)加载寄存器字节 |
| LDURH | (未缩放)加载寄存器半字 |
| LDURSB | (未缩放)加载有符号字节寄存器 |
| LDURSH | (未缩放)加载有符号半字寄存器 |
| LDURSW | (未缩放)加载有符号字寄存器 |
| LDXP | 加载独占寄存器对 |
| LDXR | 加载独占寄存器 |
| LDXRB | 加载独占寄存器字节 |
| LDXRH |

4. ARM 64位核心指令集(续)
指令 描述
LSL† 逻辑左移
LSLV 可变逻辑左移
LSR† 逻辑右移
LSRV 可变逻辑右移
MADD 乘加
MNEG† 乘负
MOV† 移动
MOVK 带保留的宽移动
MOVN 带取反的宽移动
MOVZ 带零的宽移动
MRS 移动系统寄存器
MSR 移动值到特殊寄存器
MSUB 乘减
MUL† 乘法
MVN† 按位取反
NEG{S}† 取负
NGC{S}† 带进位取负
NOP 无操作
ORN 按位或非
ORR 按位或
PACDA, PACDZA 使用密钥A为数据地址生成指针认证码
PACDB, PACDZB 使用密钥B为数据地址生成指针认证码
PACGA 使用通用密钥生成指针认证码
PACIA, PACIA1716 使用密钥A为指令地址生成指针认证码
PACIASP, PACIAZ 使用密钥A为指令地址生成指针认证码
PACIZA 使用密钥A为指令地址生成指针认证码
PACIB, PACIB1716 使用密钥B为指令地址生成指针认证码
PACIBSP, PACIBZ 使用密钥B为指令地址生成指针认证码
PACIZ 使用密钥B为指令地址生成指针认证码
PRFM 预取内存
PSB CSYNC 性能同步屏障
PSSBB 物理推测存储旁路屏障
RBIT 位反转
RET 子程序返回
RETAA, RETAB 带指针认证的子程序返回
REV 字节反转
REV16 16位半字内字节反转
REV32 32位字内字节反转
REV64† 字节反转
RMIF 旋转、掩码插入标志
ROR† 循环右移
RORV 可变循环右移
SB 推测屏障
SBC{S} 带借位减法
SBFIZ† 有符号位域零插入
SBFM 有符号位域移动
SBFX† 有符号位域提取
SDIV 有符号除法
SETF8, SETF16 评估8位或16位标志值
SEV 发送事件
SEVL 本地发送事件
SMADDL 有符号长乘加
SMC 安全监视器调用
SMNEGL† 有符号长乘负
SMSUBL 有符号长乘减
SMULH 有符号高乘法
SMULL 有符号长乘法(SMADDL的别名)
SSBB 推测存储旁路屏障
ST2G 存储分配标签
STADD, STADDL† 内存中字或双字的原子加法(无返回)
STADDB† 内存中字节的原子加法(无返回)
STADDLB† 内存中字节的原子加法(无返回)
STADDH† 内存中半字的原子加法(无返回)
STADDLH† 内存中半字的原子加法(无返回)
STCLR, STCLRL† 内存中字或双字的原子位清除(无返回)
STCLRB, STCLRLB† 内存中字节的原子位清除(无返回)
STCLRH, STCLRLH† 内存中半字的原子位清除(无返回)
STEOR, STEORL† 内存中字或双字的原子异或(无返回)
STEORB, STEORLB† 内存中字节的原子异或(无返回)
STEORH, STEORLH† 内存中半字的原子异或(无返回)
STG 存储分配标签
STGP 存储分配标签和寄存器对
STGV 存储标签向量
STLLR 存储LORelease寄存器
STLLRB 存储LORelease寄存器字节
STLLRH 存储LORelease寄存器半字
STLR 存储释放寄存器
STLRB 存储释放寄存器字节
STLRH 存储释放寄存器半字
STLUR (未缩放)存储释放寄存器
STLURB (未缩放)存储释放寄存器字节
STLURH (未缩放)存储释放寄存器半字
STLXP 存储释放独占寄存器对
STLXR 存储释放独占寄存器
STLXRB 存储释放独占寄存器字节
STLXRH 存储释放独占寄存器半字
STNP 带非临时提示的存储寄存器对
STP 存储寄存器对
STR 存储寄存器
STRB 存储寄存器字节
STRH 存储寄存器半字
STSET, STSETL† 内存中字或双字的原子位设置(无返回)
STSETB, STSETLB† 内存中字节的原子位设置(无返回)
STSETH, STSETLH† 内存中半字的原子位设置(无返回)
STSMAX† 内存中字或双字的原子有符号最大值
STSMAXL† 内存中字或双字的原子有符号最大值
STSMAXB† 内存中字节的原子有符号最大值(无返回)
STSMAXLB† 内存中字节的原子有符号最大值(无返回)
STSMAXH† 内存中半字的原子有符号最大值(无返回)
STSMAXLH† 内存中半字的原子有符号最大值(无返回)
STSMIN, STSMINL† 内存中字或双字的原子有符号最小值(无返回)
STSMINB† 内存中字节的原子有符号最小值(无返回)
STSMINLB† 内存中字节的原子有符号最小值(无返回)
STSMINH† 内存中半字的原子有符号最小值(无返回)
STSMINLH† 内存中半字的原子有符号最小值(无返回)
STTR (非特权)存储寄存器
STTRB (非特权)存储寄存器字节
STTRH (非特权)存储寄存器半字
STUMAX† 内存中字或双字的原子无符号最大值
STUMAXL† 内存中字或双字的原子无符号最大值
STUMAXB† 内存中字节的原子无符号最大值
STUMAXLB† 内存中字节的原子无符号最大值
STUMAXH† 内存中半字的原子无符号最大值
STUMAXLH† 内存中半字的原子无符号最大值
STUMIN† 内存中字或双字的原子无符号最小值
STUMINL† 内存中字或双字的原子无符号最小值
STUMINB† 内存中字节的原子无符号最小值
STUMINLB† 内存中字节的原子无符号最小值
STUMINH† 内存中半字的原子无符号最小值
STUMINLH† 内存中半字的原子无符号最小值
STUR (未缩放)存储寄存器
STURB (未缩放)存储寄存器字节
STURH (未缩放)存储寄存器半字
STXP 存储独占寄存器对
STXR 存储独占寄存器
STXRB 存储独占寄存器字节
STXRH 存储独占寄存器半字
STZ2G 存储分配标签并清零
STZG 存储分配标签并清零
SUB{S} 减法
SUBG 带标签减法
SUBP{S} 指针减法
SVC 超级用户调用
SWP, SWPA 内存中字或双字的交换
SWPAL, SWPL 内存中字或双字的交换
SWPB, SWPAB 内存中字节的交换
SWPALB, SWPLB 内存中字节的交换
SWPH, SWPAH 内存中半字的交换
SWPALH, SWPLH 内存中半字的交换
SXTB† 有符号字节扩展
SXTH† 有符号半字扩展
SXTW† 有符号字扩展
SYS 系统指令
SYSL 带结果的系统指令
TBNZ 测试位非零则分支
TBZ 测试位为零则分支
TLBI† TLB失效操作
TSB CSYNC 跟踪同步屏障
TST† 测试位
UBFIZ† 无符号位域零插入
UBFM 无符号位域移动
UBFX† 无符号位域提取
UDF 永久未定义
UDIV 无符号除法
UMADDL 无符号长乘加
UMNEGL† 无符号长乘负
UMSUBL 无符号长乘减
UMULH 无符号高乘法
UMULL† 无符号长乘法
UXTB† 无符号字节扩展
UXTH† 无符号半字扩展
WFE 等待事件
5. 总结与实践建议

在64位ARM汇编语言编程中,安全和指令集是两个重要的方面。

安全方面,防止栈上代码运行、缓解缓冲区溢出等措施对于保护程序免受攻击至关重要。我们需要在设计API时选择合适的函数,如使用 strncpy 代替 strcpy ,并在代码提交过程中添加检查。同时,要警惕数据泄露问题,避免在错误消息中包含内存地址。开启所有缓冲区溢出保护技术可能会影响性能,因此对于需要高性能的代码段,要确保有外部模块进行数据清理和验证。另外,要注意共享库对安全特性的影响,如PIE和栈金丝雀等。

指令集方面,ARM 64位核心指令集和NEON及FPU指令提供了丰富的功能,涵盖了算术运算、逻辑运算、内存操作、控制流等多个方面。了解这些指令的功能和使用方法,有助于编写高效、安全的汇编语言程序。

以下是一些实践建议:
1. 实验与练习 :通过实际编写代码来加深对安全和指令集的理解。例如,可以尝试以下练习:
- 在代码中开启栈金丝雀,观察其如何捕获栈溢出。
- 对现有的示例程序开启PIE,确保其正常工作。
2. 项目实践 :思考并开展自己的汇编语言项目,如:
- 控制连接到NVidia Jetson Nano GPIO引脚的机器人。
- 使用汇编语言代码优化AI对象识别算法,甚至利用NEON处理器。
- 为Linux内核的ARM特定部分做出贡献,以提高操作系统性能。
- 改进GCC以生成更高效的ARM代码。
- 构思并开发具有创新性的应用程序。

操作流程示例

开启栈金丝雀并测试

以下是开启栈金丝雀并测试的操作步骤:
1. 编写一个简单的C程序,例如 upper.c
2. 使用编译器选项开启栈金丝雀,如 gcc -fstack-protector-all upper.c -o upper
3. 运行程序,尝试触发栈溢出,观察栈金丝雀是否能捕获该溢出。

开启PIE并测试

以下是开启PIE并测试的操作步骤:
1. 选择一个现有的示例程序。
2. 使用编译器选项开启PIE,如 gcc -fPIE -pie example.c -o example
3. 运行程序,确保其正常工作。

流程图

graph TD;
    A[编写程序] --> B[开启安全特性];
    B --> C[编译程序];
    C --> D[运行程序];
    D --> E{是否正常运行};
    E -- 是 --> F[程序正常];
    E -- 否 --> G[检查问题并修复];
    G --> B;

通过以上的学习和实践,我们可以更好地掌握64位ARM汇编语言编程的安全和指令集知识,编写出更安全、高效的程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值