mips体系堆栈回溯分析与实现

可执行文件崩溃日志分析:系统学习与应用 深入解析可执行文件运行时崩溃的原因,结合典型日志信息定位问题根源,掌握从异常堆栈到内存状态的完整分析流程,提升系统级调试能力。 阅读详情

  在上家公司做 linux 驱动开发主要基于mips架构,在内核下有一个好玩的函数 dump_stack, 只要调用到这个函数就能把函数的调用栈给打印出来,十分方便,不再需要kgdb断点了。
  现在做电视机顶盒开发,各种软硬件平台 arm mips linux ecos 都有,有时候想要获得一个函数从哪里调用来的单凭静态分析根本不行,如果动态运行那就要在每个调用前面加入打印,修改量巨大而且容易出错。于是仿照 linux 内核代码 arch/mips/oprofile/backtrace.c 实现了mips架构下的栈回溯功能。

mips栈帧原理
  Call stack 是指存放某个程序的正在运行的函数的信息的栈。Call stack 由 stack frames 组成,每个 stack frame 对应于一个未完成运行的函数。
  在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。栈用来传递函数参数,存储返回值信息,保存寄存器以供恢复调用前处理机状态。 关于栈可见以前的文章: cdecl、stdcall、fastcall函数调用约定区别
  每次调用一个函数,都要为该次调用的函数实例分配栈空间。为单个函数分配的那部分栈空间就叫做 stack frame,也就是说,stack frame 这个说法主要是为了描述函数调用关系的。

Stack frame 具体从2点来阐述
  第一,它使调用者和被调用者达成某种约定。这个约定定义了函数调用时函数参数的传递方式,函数返回值的返回方式,寄存器如何在调用者和被调用者之间进行共享;
  第二,它定义了被调用者如何使用它自己的 stack frame 来完成局部变量的存储和使用。
mips 栈帧

  上图描述的是一种典型的(MIPS O32)嵌入式芯片的 stack frame 组织方式。在这张图中,计算机的栈空间采用的是向下增长的方式(intel 是确定向下增长的,arm可以配置栈增长方向,mips 是可配置还是只能向下增长?),SP(stack pointer) 就是当前函数的栈指针,它指向的是栈底的位置。Current Frame 所示即为当前函数(被调用者)的 frame ,Caller’s Frame 是当前函数的调用者的 frame 。
  在没有 BP(base pointer) 寄存器的目标架构中,进入一个函数时需要将当前栈指针向下移动 n 字节,这个大小为n字节的存储空间就是此函数的 stack frame 的存储区域。此后栈指针便不再移动(在linux 内核代码 TODO 里面写着要加上在函数内部调整栈的考虑 – 虽然这通常不会发生),只能在函数返回时再将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈都必须指定偏移量,这与 x86 架构的计算机对栈的使用方式有着明显的不同。
  RISC计算机一般借助于一个返回地址寄存器 RA(return address) 来实现函数的返回。几乎在每个函数调用中都会使用到这个寄存器,所以在很多情况下 RA 寄存器会被保存在堆栈上以避免被后面的函数调用修改,当函数需要返回时,从堆栈上取回 RA 然后跳转。移动 SP 和保存寄存器的动作一般处在函数的开头,叫做 function prologue;
  注意如果当前函数是叶子函数(不存在对其它函数的调用,就不保存ra寄存器,反之就保存)。恢复这些寄存器状态的动作一般放在函数的最后,叫做 function epilogue。关于这些动作可以从IDA 反汇编的结果看出来:
ida mips反汇编
通过上面分析就有思路了:
首先获取当前的栈指针 sp,和指令指针 pc (也叫做 IP)
在mips下sp容易获取 已经约定$29 寄存器作为栈指针,所以可用如下内嵌汇编获取sp:

__asm__ volatile ("move %0, $29" : "=r"(reg));

  MIPS没有记录当前PC地址的寄存器,就是说不能像ARM那样读PC寄存器。MIPS使用ra保存函数返回地址,利用这个特性可以获取到当前的PC。 比如:

#pragma GCC push_options
#pragma GCC optimize ("O0")
static  unsigned int /*__attribute__((optimize("O0")))*/ * __getpc(void) 
{
    unsigned int *rtaddr;
    
    __asm__ volatile ("move %0, $31" : "=r"(rtaddr));
    
    return rtaddr;
}
#pragma GCC pop_options

对应的汇编代码是:

  注意到上面代码中有 push_options 这是为了防止编译器偷偷把我们的代码给优化成 inline 了,那样就无法获取pc了。在要获取pc的地方调用就可以了:

pc   	= __getpc();

得到sp 和pc之后剩下的就开始回溯了,具体参考下面函数实现(linux 内核未修改代码,只加入注释):

/*
 * TODO for userspace stack unwinding:
 * - handle cases where the stack is adjusted inside a function
 *     (generally doesn't happen)
 * - find optimal value for max_instr_check
 * - try to find a way to handle leaf functions
 */

static inline int unwind_user_frame(struct stackframe *old_frame,
				    const unsigned int max_instr_check)		//// max_instr_check 函数最大可能的代码长度
{
	struct stackframe new_frame = *old_frame;
	off_t ra_offset = 0;
	size_t stack_size = 0;
	unsigned long addr;

	if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0)
		return -9;

	for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc)	/// 上面通过 __getpc 获取的pc指针
		&& (!ra_offset || !stack_size); --addr) {
		union mips_instruction ip;

		if (get_mem(addr, (unsigned long *) &ip))			/// 取出一条指令  ip 
			return -11;
 
		if (is_sp_move_ins(&ip)) {					/// 这条指令是不是 addiu $sp,imme 形式的
			int stack_adjustment = ip.i_format.simmediate;		/// 如果是求出 imme 立即数,那么本函数栈大小也就知道了
			if (stack_adjustment > 0)
				/* This marks the end of the previous function,
				   which means we overran. */
				break;
			stack_size = (unsigned long) stack_adjustment;
		} else if (is_ra_save_ins(&ip)) {				/// 是不是  sw / sd $ra, offset($sp) 类似的指令
			int ra_slot = ip.i_format.simmediate;			/// 如果是获取 offset
			if (ra_slot < 0)
				/* This shouldn't happen. */
				break;
			ra_offset = ra_slot;
		} else if (is_end_of_function_marker(&ip))
			break;
	}

	if (!ra_offset || !stack_size)
		return -1;

	if (ra_offset) {
		new_frame.ra = old_frame->sp + ra_offset;			/// 根据上面的 offset 和当前函数的sp指针得到存放 ra 数值的地址
		if (get_mem(new_frame.ra, &(new_frame.ra)))			/// 获取ra 的数值,也就是 jal func 这条指令所在的地址
			return -13;
	}

	if (stack_size) {
		new_frame.sp = old_frame->sp + stack_size;			/// 上个函数的栈指针 貌似这里这么代码是错误的,反正我按我的方式改了能正确运行,
		if (get_mem(new_frame.sp, &(new_frame.sp)))			/// 这里这么写我也不懂。
			return -14;
	}

	if (new_frame.sp > old_frame->sp)
		return -2;

	new_frame.pc = old_frame->ra;
	*old_frame = new_frame;

	return 0;
}

  上面代码修改之后可以获取 上一层函数的 pc (jal func :调用本函数的指令不就是上层函数的某一条指令? ra 的值并不是jal func 的地址,而是jal func的地址 +8 ,传说中的 跳转延时槽)指针和sp 。
  依此循环调用上面的函数一层层向上回溯,即可。

参考文章:
mips体系堆栈回溯分析与实现
MIPS 架构上函数调用过程中的堆栈和栈帧

在我的dropbox下载完成的代码:
https://www.dropbox.com/sh/uqgvjs1ybu118e1/AADMzHpDbvgtN8QSAPtA0lJ1a?dl=0

FirmPilot:基于多智能体协同的物联网固件自动化重托管技术解析 固件重托管是物联网设备安全分析中的关键技术,旨在将嵌入式固件在通用模拟环境中运行,以便进行动态分析、漏洞挖掘和功能测试。其核心原理是通过模拟器(如QEMU)构建虚拟硬件环境,补全固件运行所需的系统调用、硬件外设和文件系统依赖。传统方法依赖人工逆向与规则匹配,效率低且通用性差。FirmPilot创新性地引入证据引导的多智能体协同框架,通过监控固件执行崩溃点,驱动多个专用智能体(如系统调用仿真、外设建模、二进制补丁等)自动分析与修复环境缺失问题,实现了从高度依赖专家经验到可重复、可扩展的系统工程的转变。该技术显 阅读详情

相关推荐

Linux内核四大支柱:调度、内存、I/O与安全机制解析

内核(Kernel)是操作系统与硬件之间的核心枢纽,负责资源抽象、权限管控与系统协调。其本质是一套运行在CPU最高特权级的精密契约体系,通过系统调用提供受控访问接口。核心原理围绕四大子系统展开:CPU调度器实现公平时间片分配与上下文切换;内存管理构建虚拟地址空间,支撑隔离、swap与OOM控制;I/O子系统统一设备驱动模型与页缓存机制,平衡性能与一致性;安全框架依托ring0/ring3隔离、SELinux等强制访问控制保障最小权限。这些机制共同支撑云计算、AI训练(如PyTorch CUDA错误)、嵌入式

weixin_34357436的博客 408

MIPS 架构上函数调用过程中的堆栈和栈帧

    在计算机科学中,Call stack 是指存放某个程序的正在运行的函数的信息的栈。Call stack 由 stack frames 组成,每个 stack frame 对应于一个未完成运行的函数。   在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。栈用来传递函数参数,存储返回值信息,保存寄存器以供恢复调用前处理机状态。每

游手好弦 信步涂鸦 1万+

【信息科学与工程学】计算机科学与自动化——第二十篇 计算机体系架构00

路径选择与流量调度是网络优化中的核心环节,它们通过一系列算法来提升网络效率、降低延迟,并有效利用资源。

weixin_49199313的博客 803

linux获取栈回溯编译,一文详解Linux内核的栈回溯与妙用

一文详解Linux内核的栈回溯与妙用1 前言说起linux内核的栈回溯功能,我想这对每个Linux内核或驱动开发人员来说,太常见了。如下演示的是linux内核崩溃的一个栈回溯打印,有了这个崩溃打印我们能很快定位到在内核哪个函数崩溃,大概在函数什么位置,大大简化了问题排查过程。网上或多或少都能找到栈回溯的一些文章,但是讲的都并不完整,没有将内核栈回溯的功能用于实际的内核、应用程序调试,这是本篇文章的...

weixin_42366752的博客 475

pytorch图像分类篇: 花分类数据集下载和AlexNet网络搭建训练

一、花分类数据集下载 data_setdata_set该文件夹是用来存放训练数据的目录 使用步骤如下: (1)在data_set文件夹下创建新文件夹"flower_data" (2)点击链接下载花分类数据集 (3)解压数据集到flower_data文件夹下 (4)执行"flower_data.py"脚本自动将数据集划分成训练集train和验证集val ├── flower_data ├── flower_photos(解压的数据集文件夹,3670个样本) ...

橙子的博客 8218

Ubuntu单系统安装全攻略:从删除Windows到UEFI引导设置(避坑指南)

本文提供Ubuntu单系统安装的完整指南,涵盖从安全删除Windows系统到UEFI引导设置的详细步骤。通过专业工具准备、分区方案设计、引导加载器配置等关键环节,帮助用户避免常见安装问题,实现纯净高效的Ubuntu单系统环境。特别强调UEFI引导的深度配置与安装后的性能优化,适合希望彻底转向Linux的用户。

weixin_29210727的博客 409

「C/C++」C/C++调试篇 之 堆栈回溯介绍

堆栈回溯是程序开发和调试中不可或缺的技术。通过深入理解堆栈回溯的概念、原理、实现方法以及实战应用,开发者可以更有效地定位和解决程序中的问题。同时,掌握这些技能也有助于提升代码质量和开发效率。希望本文能为读者在堆栈回溯的探索之路上提供一些有用的指导和帮助。

何曾参静谧的博客 1669

使用sklearn学习多项式回归(三)

1,什么是线性 我们无数次提到”线性“这个名词。在支持向量机中,我们也曾经提到最初的支持向量机只能够分割线性可分的数据,然后引入了”核函数“来帮助我们分类那些非线性可分的数据。我们也曾经说起过,比如说决策树,支持向量机是”非线性“模型。所有的这些概念,让我们对”线性“这个词非常熟悉,却又非常陌生——因为我们并不知道它的真实含义。接下来我们看看线性回归模型改进的核心之一:帮助线性回归解决非线性问题。 1.1,变量之间的线性关系 ”线性“这个词用于描述不同事物时有着不同的含义。我们最常使用的线性是指“变量

理科男同学 5100

qbittorrent mipsel交叉编译

个人原创,转载请注明出处!!! 前言 本文主要记录了qBittorrent 在Pandorabox(Openwrt)下的交叉编译(基于mipsel架构),编译方式原始,非生成ipk安装包而是直接可运行的二进制执行文件,需要自己手动去链接,已经会初步编译ipk包了。 qBittorrent是一款开源的,轻量级的,基于Qt框架所编写的BT下载工具,wiki页面:https://gith...

weixin_30904593的博客 1418

《BiLSTM with Multi-Polarity Orthogonal Attention for Implicit Sentiment Analysis》论文阅读笔记

文章名:《BiLSTM with Multi-Polarity Orthogonal Attention for Implicit Sentiment Analysis》 作者Jiyao Wei a,1, Jian Liaoa,1, Zhenfei Yanga, Suge Wanga,b,∗, Qiang Zhaoa 年份 2019 1 Introduction 目前文本情感分析多分析带...

weixin_39036944的博客 1630

CDR学习笔记——02

这篇写一些高级CDR的架构原理。

... 1015

linux打印调用栈 函数名_linux栈空间自动增长实现原理深入分析和总结

1. 引言 很多实时非linux系统的栈都是固定而且很小,但申明的局变变量过大时就会出现栈溢出导致非法改写问题。linux的栈有个自动增长的属性,那么linux的栈是如何保证自动增长的呢?这个自动增长有上限吗? 栈相对于堆也显得更加神秘,因为堆内存通常都会调用malloc函数去申请,调用free去释放,开发者至少有显示地去申请和释放它,解决过内存改写和内存泄露的人也会深入到c库的内存...

weixin_39574720的博客 319

在 SAP 里玩转 FTP:标准程序 RSFTP00x 全景解析与落地实践

摘要:SAP系统通过封装的标准FTP程序(RSFTP001-008)提供便捷的文件传输能力,适用于各类集成场景。关键点包括:1)依赖SAPFTP可执行程序和RFC目的地;2)使用RSFTP005进行前置检查并生成RFC配置;3)标准报表各司其职,如RSFTP002用于命令测试、RSFTP007/008提供参考实现;4)开发时组合FTP_CONNECT/COMMAND/DISCONNECT等函数模块。特别注意前台(SAPFTP)与后台(SAPFTPA)的环境差异,这是常见故障点。该方案通过标准化工具链显著降低

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 210

MIPS32的内部寄存器

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34160277的博客 400

Python 爬虫实战:简易爬取知乎回答内容

本文详细介绍了使用Python爬取知乎回答内容的实战方法。通过分析知乎异步加载API接口,讲解如何构建带反爬策略的爬虫代码,包括请求头设置、JSON数据解析和内容清洗等核心步骤。文章提供了完整可运行的代码示例,并针对多页爬取、数据保存和反爬升级给出优化建议。同时强调爬虫使用的合规性,帮助新手快速掌握动态网页爬取的核心技巧,实现从静态到动态网页爬虫的进阶。

2503_91057718的博客 2196

外设测试 - ATAES132A 加解密芯片测试

原理 使用芯片官方提供的读写操作库,进行先写后读并校验,确认数据一致。 示例代码 int main(void) { uint8_t ret; aes132p_enable_interface(); ret = write_memory_test(); if (ret != AES132_FUNCTION_RETCODE_SUCCESS) { aes132p_disable_interface(); printf

一个软件工程师的技术笔记 858

MIPS32的内部寄存器。

基础知识介绍:   MIPS32的内部寄存器。 最简单的办法就是通过GDB的命令,可以获得下面的列表 (gdb) info registers           zero       at       v0       v1       a0       a1       a2       a3  R0   00000000 00000001 0000000f...

weixin_34364071的博客 567

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

内核中dump_stack()的实现,并在用户态模拟dump_stack()【转】

转自:https://blog.youkuaiyun.com/jasonchen_gbd/article/details/44066815?utm_source=blogxgwz8 版权声明:本文为博主原创文章,转载请附上原博链接。 https://blog.youkuaiyun.com/jasonchen_gbd/article/details/44066815内核中的dump_stack()获得内核中当前进程的栈回...

weixin_34061042的博客 278

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

转:也谈栈和栈帧

https://blog.youkuaiyun.com/dsgsnb/article/details/5687591     https://blog.youkuaiyun.com/kexin1212/article/details/38067533 一个码农要是没遇见过coredump,那他就是神仙了。core file(coredump的转储文件)中保存的最重要内容之一,就是函数的call trace。还...

hunter 933

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

计算机专业大学四年知识体系全景图(万字详解)

知识体系的顶层结构。这篇文章不是万能的,但它是一个方向盘,帮你从知识的碎片中构建出完整的地图。只要你愿意行动,从今天起,哪怕每天掌握一点点,也终将走出自己的计算机之路。

Anthony1453的博客 2851

【嵌入式技能树】

嵌入式工程师需要哪些技能书/技能加点? 学习程度:熟悉 < 有过经验 < 熟练 < 精通 文章目录通用技能树软件硬件通信工具调试其他嵌入式软件工程师嵌入式驱动工程师嵌入式应用工程师嵌入式硬件工程师 通用技能树 软件 C/C++语言编程经验、编程能力、应用开发、OOP面向对象编程方式、软件框架、 C指针、文件IO、进程线程、高级IO、 对C++中的智能指针、多线程、网络编程...

Xuanyu_Chen的博客 3453

基于arm的C++反汇编 函数的工作原理

栈帧的形成和关闭 各种调用方式的考擦 使用 fp或sp寻址 函数的参数 与返回值 arm指令中立即数存放位置 gdbserver 调试环境栈帧的形成和关闭栈在内存中是一块特殊的存储空同, 它的存储原则是“先进后出”, 即最先被存储的数据最后被释放, 汇编过程通常使用 push 指令与 POP指令对栈空间执行数据压入和数据弹出操作。栈结构在内存中占用一段连续的存储空间, 通过sp与 fp这两个栈指针寄

XscKernel的专栏 记录工作中做的点滴 7747

深入理解Linux网络技术内幕 P1-P2

细品

西门吹雪 540

linux获取栈回溯编译,如何展开堆栈以获取指定堆栈指针(SP)的回溯?

我正在为Android编写(仅限ARM),但我相信通用Linux的原理也是一样的。我试图从信号处理程序捕获堆栈跟踪,以便我的应用程序崩溃时可以logging它。 这就是我使用 。初始化:struct sigaction signalhandlerDescriptor; memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescr...

weixin_30025473的博客 378

Linux 栈回溯

日常工作中,我们在开发软件程序时,经常会遇到程序奔溃的问题,导致程序奔溃的原因有很多,我们一般都是定位到相关代码,再去查询具体原因。而定位到bug相关代码往往需要依赖栈回溯这个功能,知道程序是在哪里挂掉的。在Linux系统中,栈回溯(stack trace)是用于跟踪程序执行期间函数调用的一种技术,记录了程序在出现错误或异常时,从当前位置开始追溯回到程序的执行起点,包括每个函数的调用关系和相应的返回地址。栈回溯可以帮助开发人员快速定位问题所在。

小嵌同学的博客 3241

嵌入式Linux打印栈回溯信息

/* * Copyright (C) 2021, 2021 huohongpeng * Author: huohongpeng <1045338804@qq.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free ...

专注嵌入式领域 601
上一篇: 一个 pthread_cancel 引起的线程死锁
下一篇: linux 常用命令 整理
xianjian_x
博客等级 码龄14年 231粉丝 43原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值