操作系统开发:BIOS/MBR基础与调试

news2024/12/28 5:20:34

这里在实验之前需要下载 Bochs-win32-2.6.11 作者使用的是Linux版本的,在Linux写代码不太舒服,所以最好在Windows上做实验,下载好虚拟机以后还需要下载Nasm汇编器,以及GCC编译器,为了能够使用DD命令实现磁盘拷贝,这里你可以安装windows 10 下面的子系统Ubuntu,需要使用命令时可以直接切换。

**注释:**该系列笔记是在学习《操作系统真相还原》时通过阅读后简化并适当描述整理的学习笔记,首先,致敬作者郑刚博士,在读本书时能深刻的感觉到作者写书时一丝不苟的态度,书很厚写的,讲解细致幽默,很能让人愿意继续读下去,同时也不得不佩服作者计算机底层功力的深厚,转载本文请一并附带郑刚版权信息。

BIOS 软件接力第一棒

BIOS 基本输入输出系统,BIOS代码所做的工作是一成不变的,所以他是被固化到ROM中的一块只读区域中,在开机时此ROM会被映射到低端1MB内存的顶部,原因是系统在开启时默认是实地址模式(该模式最大寻址范围0-fffff),所以其寻址范围也就被限制在了0xF0000-x0xFFFFF区域中,这64KB的内存就是BIOS的执行代码.

在开机的一瞬间,CPU的CS:IP寄存器会被强制初始化为0xF000:0xFFF0,在实地址模式下该地址需要乘以16也就是左移四位加上偏移地址得到,于是0xF000:0xFFF0就等效于0xFFFF0此处的地址距离0xFFFFF只有16个字节的空间,里面存放着一条jmp far f000:e05b = fe05b的汇编指令,该指令将跳转到真正的BIOS开始的位置.

接着BIOS将会通过自身的代码对硬件进行自检测,在初始化硬件后,则开始向内存0x000-0x3ff中初始化数据结构以及拷贝中断向量表,紧接着BIOS将会通过调用int 19h中断,此中断用以检测计算机中的硬盘,如果检测到0盘0道1扇区末尾的两个字节是0x55,0xaa则认为此扇区确实存在,于是就会将此区域中的内容,加载到内存7c00的位置,并通过一条jmp far 0:0x7c00h的指令跳转到该位置执行,这样BIOS就将CPU控制权交给了MBR了,而BIOS将会再次睡去.

MBR 收到跳转来源,继续执行。

此处的7c000就是MBR代码的开始位置,之所以是7C00是因为,DOS中要求最小内存是32KB,而MBR大小必须是512字节(1KB),所以选择32kB中的最后1KB的位置最为合适,32KB(0x8000)-1KB(0x400)=>0x7c00,这就是7C00的由来,同时还需要保证第510-511字节必须为0x55,0xaa才可以.

保存以下汇编代码,并使用 nasm -o mbr.bin mbr.asm编译简易版MBR文件.

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
	mov ax,cs
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov fs,ax
	mov sp,0x7c00

	mov ax,Message
	mov bp,ax         ; 保存字符串地址
	mov cx,15         ; 保存字符串长度
	mov ax,01301h     ; 子功能号13是显示字符及属性
	mov bx,000ch      ; 页号位0,使用黑色为背景色,红色为字体颜色
	mov dl,0
	int 10h           ; 10h中断,用来显示字符
	ret

Message: db "hello lyshark !"
times 510-($-$$) db 0  ; 填充510字节为0
db 0x55,0xaa           ; mbr的结束标志

进入Bochs目录下执行bximage.exe生成一个映像文件,默认是a.img,你可以改名为其他的,这里我定义为linux.img

并将编译好的mbr.bin写入到镜像中

dd if=mbr.bin of=linux.img bs=512 count=1 conv=notrunc

在Bochs目录下新建并编辑bosh.src保存,然后执行bochs.exe -f bosh.src模拟执行MBR代码.

megs:32
romimage:file=$BXSHARE/BIOS-bochs-latest
vgaromimage:file=$BXSHARE/VGABIOS-lgpl-latest
floppya:1_44=linux.img,status=inserted
boot:floppy
log:bochsout.txt
mouse:enabled=0
keyboard: keymap=$BXSHARE/keymaps/x11-pc-de.map

上方屏幕会比较混乱,这里我们先来进行清屏操作,清屏中断调用也是int10

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov ds,ax
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10
        mov ax,Message
        mov bp,ax         ; 保存字符串地址
        mov cx,15         ; 保存字符串长度
        mov ax,01301h     ; 子功能号13是显示字符及属性
        mov bx,000ch      ; 页号位0,使用黑色为背景色,红色为字体颜色
        mov dl,0
        int 10h           ; 调用10h号中断,用来显示字符
        ret

Message: db "hello lyshark !"
times 510-($-$$) db 0  ; 填充510字节为0
db 0x55,0xaa           ; mbr的结束标志

执行结果,如下,但是,打印字符串,在底部,因为光标在底部。

设置光标到顶部,这里百度一下光标中断,发现了。

接着改进代码

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov ds,ax
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10
        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10
        mov ax,Message
        mov bp,ax         ; 保存字符串地址
        mov cx,15         ; 保存字符串长度
        mov ax,01301h     ; 子功能号13是显示字符及属性
        mov bx,000ch      ; 页号位0,使用黑色为背景色,红色为字体颜色
        mov dl,0
        int 10h           ; 调用10h号中断,用来显示字符
        ret

Message: db "hello lyshark !"
times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

完美结果。

mbr.asm

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov ds,ax
        mov es,ax
        mov ss,ax
        mov fs,ax
        mov sp,0x7c00

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10
        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10
        mov ax,Message
        mov bp,ax         ; 保存字符串地址
        mov cx,15         ; 保存字符串长度
        mov ax,01301h     ; 子功能号13是显示字符及属性
        mov bx,000ch      ; 页号位0,使用黑色为背景色,红色为字体颜色
        mov dl,0
        int 10h           ; 调用10h号中断,用来显示字符
		hlt
        ret

Message: db "hello lyshark !"
times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

mbr.src

megs:32
romimage:file=./BIOS-bochs-latest
vgaromimage:file=./VGABIOS-lgpl-latest
boot:disk
mouse:enabled=0
ata0-master: type=disk, path="linux.img", mode=flat, status=inserted
keyboard: keymap=./x11-pc-de.map

填充数据

dd if=mbr.bin of=linux.img bs=512 count=1 conv=notrunc
dd if=/dev/zero of=linux.img seek=1 bs=512 count=2879

运行

bochsdbg -q -f mbr.src
vb sp:0x7c00
c

让我们对显卡说点什么?

上面我们通过调用BIOS提供的int 0x10中断来实现打印字符操作,但我们在后期必须要借助显卡来输出图像,而显卡是外部设备,必须通过总线来操作。

由于CPU使用的信号是TTL电平,而外部设备都是机械设备,故他们不会使用该电平驱动,这就导致CPU与硬件设备没有办法实现沟通,硬件工程师们提供的方法是,在这两者之间架起一座桥,也就是在CPU和外设之间加上一层IO接口,该接口的作用就是实现CPU和外设之间相互做协调转换。

其次外部设备的种类也是多种多样的,其输出的信号可能是数字信号,也可能是模拟信号,而我们的CPU只能处理数字信号,数字信号需要经过数模转换器<D/A>成模拟量才能送到外设来驱动硬件工作,模拟量也同样需要经过模数转换器<A/D>转换成数字量才能被CPU直接处理,所以接口电路中需要包括A/D转换器和D/A转换器。

转换后的数字信号,会经过总线进行传递,总线的别名是BUS,之所以叫做BUS是因为其是公共线路,所有硬件设备都会走此线路,但同一时刻,CPU只能和一个IO接口(寄存器/端口)通信,当有多个IO接口同时想和CPU通信时,那么IO仲裁模块会对其进行竞争与选优,仲裁模块固化到,输入输出控制中心(ICH)也就是南桥芯片上的。

多数情况下,南桥和北桥是成对出现的,南桥主要负责连接PCI,PCI-Express,AGP等低速设备,而北桥则用于链接高速设备,如内存等。

IO接口都是串行口,其在设计之初就是负责与CPU进行通信的,我们想要与CPU通信,其实是向这些接口中写入数据,同时为了区别CPU中的寄存器,所以把IO接口叫做端口,某些外设可以通过内存映射来访问,即把某些端口映射到指定内存中,访问某个内存区域就相当于访问了指定的端口。

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov sp,0x7c00
        mov ax,0xb800
        mov gs,ax

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10

        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10

        mov byte [gs:0x00],'M'
        mov byte [gs:0x01],0xa4 ; 显示A=绿色闪烁 4=红色

        mov byte [gs:0x02],'B'
        mov byte [gs:0x03],0xa5

        mov byte [gs:0x04],'R'
        mov byte [gs:0x05],0xa6
        ret

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

Bochs调试命令基础

Bochs调试命令常用的有以下几种.

<bochs:1> vbreak 0x0000:0x7c000      7c000设置断点
<bochs:1> pb 0x7c000                 设置物理断点
<bochs:1> vb sp:0x7c00               设置虚拟断点(保护模式)
<bochs:1> info break                 显示所有断点状态
<bochs:1> delete num                 删除一个断点

<bochs:1> c              运行遇到断点停下
<bochs:1> n              执行下一指令
<bochs:1> r              显示寄存器
<bochs:1> u/10           向下反汇编10条
<bochs:1> print-stack    打印堆栈

x /nuf addr 查看一个线性地址的内存
xp /nuf addr 查看一个物理地址的内存
     n 显示多少个字节的内存
     u 单位长度; one o单位f
     b 字节
     h 半字(2 字节)
     w 字 (4 字节)
     g 双字 (8 字节)

     F 打印格式:
     x 打印十六进制
     d 打印十进制
     u 打印无符号十进制
     o 打印八进制
     t 打印二进制

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
        mov ax,cs
        mov sp,0x7c00
        mov ax,0xb800
        mov gs,ax

        mov ax,0x600      ; 清屏范围,也就是宽度
        mov bx,0x0
        mov cx,0x0        ; 清屏 左上角(0,0)
        mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
        int 0x10

        mov dh,0x0        ; 设置光标列号
        mov dl,0x0        ; 设置光标行号
        mov bh,0x0        ; 页码
        int 0x10

        mov byte [gs:0x00],'L'
        mov byte [gs:0x01],0xa4 ; 显示A=绿色闪烁 4=红色

        mov byte [gs:0x02],'y'
        mov byte [gs:0x03],0xa5

        mov byte [gs:0x04],'S'
        mov byte [gs:0x05],0xa6
		
		mov byte [gs:0x6],'h'
		mov byte [gs:0x7],0xa7
		
		mov byte[gs:0x8],'a'
		mov byte [gs:0x9],0xa6
		
		mov byte[gs:0xa],'r'
		mov byte [gs:0xb],0xa5
		
		mov byte[gs:0xc],'k'
		mov byte [gs:0xd],0xa4
		hlt
        ret

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

循环显存

_start:
	; 清屏和设置光标位置
	mov ax,0x600      ; 清屏范围,也就是宽度
	mov bx,0x0
	mov cx,0x0        ; 清屏 左上角(0,0)
	mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
	int 0x10

	mov dh,0x0        ; 设置光标列号
	mov dl,0x0        ; 设置光标行号
	mov bh,0x0        ; 页码
	int 0x10

	;初始化数据段,使其指向段基址0X7C0处,即Boot代码被加载的地方
	mov		ax, 0x07c0      ; 设置加载基址
	mov		ds, ax

	;将文本显示内存段基址 放在ES中,供后面显示字符使用
	mov 	ax, 0xb800      ; 设置显存地址
	mov 	es, ax

	mov		cx, msglen        ; 获取字符串长度
	mov		si, message       ; 设置字符串基址
	xor		di, di

loop_str:
	mov al, [si]            ; 每次取出一个字符
	mov [es:di], al         ; 将字符逐一赋值到显存中
	inc si
	inc di
	
	mov byte [es:di], 0x07     ; 设置字体颜色
	inc di
	loop loop_str


	hlt      ; 程序在此处终止

	message 	db "Loading MBR..."
	msglen		db $ - message

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

Bochs 调试命令

CPU加电后,会跳转到 0xffff0 处,我们可以反汇编这段内存地址,向下反汇编10条。

分别显示,CPU模式,中断,call调用源。

设置vb虚拟地址断点,pb设置物理地址断点。blist显示所有断点。

bpd禁用断点,bpe启用断点。del删除断点。

info idt = 显示中断向量表 , gdt 全局描述符表,ldt 局部描述符表,tss 任务状态段,ivt 中断向量表

SECTION MBR vstart=0x7c00     ; 告诉编译器加载到7c00内存处
	; 清屏和设置光标位置
	mov ax,0x600      ; 清屏范围,也就是宽度
	mov bx,0x0
	mov cx,0x0        ; 清屏 左上角(0,0)
	mov dx,0x184f     ; 清屏 右下角(80=0x4f,25=0x18)
	int 0x10

	mov dh,0x0        ; 设置光标列号 左上角(0,0)
	mov dl,0x0        ; 设置光标行号 右下角(0,0)
	mov bh,0x0        ; 页码
	int 0x10

	; 初始化,使SP寄存器指向段基址0X7C0处,GS指向显存基地址
	mov ax,cs
	mov sp,0x7c00
	mov ax,0xb800
	mov gs,ax              ; 设置显存地址

	; 设置字符串长度与字符串基地址
	mov cx, msglen        ; 获取字符串长度
	mov si, message       ; 设置字符串基址
	xor di, di            ; 每次清空di寄存器

loop_str:
	mov al, [si]            ; 每次取出一个字符
	mov [gs:di], al         ; 将字符逐一赋值到显存中
	inc si
	inc di
	
	mov byte [gs:di], 000ch ; 设置字体颜色
	inc di
	loop loop_str

	hlt      ; 程序在此处终止

;message db "Hello LyShark...",0ah,0dh
message db "Hello LyShark..."
msglen  equ $ - message

times 510-($-$$) db 0  ; 填充剩余的510字节的空间为0
db 0x55,0xaa           ; mbr的结束标志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

树莓派 安装 宝塔linux面板5.9. 2023-2-14

一.环境 1.硬件环境: 树莓派3b , 8GB tf卡 ,micro usb电源 2.网络环境: 网线直连路由器 , 可访问互联网 3.软件环境: 树莓派操作系统 CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-2009-sda(linux) 系统刻录工具 Win32DiskImager (win) ip扫描工具 Advanced IP Scanne…

公司招聘:33岁以上的和两年一跳的不要,开出工资我还以为看错了...

导读&#xff1a;对于公司来说&#xff0c;肯定是希望花最少的钱招到最优秀的员工&#xff0c;但事实上这个想法是不太现实的&#xff0c;虽然如今互联网不太好找工作&#xff0c;但要员工降薪去入职&#xff0c;相信还是有很大难度的&#xff0c;很多人宁可在家休息&#xff0…

【Linux】进程的虚拟地址空间

文章目录现象引入进程地址空间进程地址空间的描述进程地址空间是怎么产生的进程地址空间的好处对开篇问题的解释现象引入 我们运行下面一段代码&#xff1a; #include <stdio.h> #include <unistd.h>int global_val 100;int main() {pid_t id fork();int count…

根据 Jupyter-lab 源码实现 notebook(.ipynb)在页面中的渲染

前言 最近因为工作项目的需要&#xff0c;要在项目中尽可能的还原notebook渲染效果。由于网上没找到相关的指导文章&#xff0c;所以只能生啃JupyterLab源码&#xff0c;独自摸索实现。经过一段时间“跌跌撞撞”的摸索尝试&#xff0c;总算勉强实现了。 因此编写此文章做一下…

转转微服务容量管理实践

1 背景2 容量管理的目标3 发展阶段4 容量管理4.1 容量水位4.2 资源容量优化4.3 集群容量4.4 压测指标4.5 压测标准5 扩容、缩容6 总结1 背景 随着转转业务的不断发展和用户不断增长&#xff0c;公司持续增加对硬件和基础设施的投入&#xff0c;用于满足业务发展的需要&#xff…

计算机网络8-在浏览器中输入URL后会发生什么

参考&#xff1a; 在浏览器中输入URL并按下回车后会发生什么&#xff1f; DNS域名详细解析过程 1.URL解析拿到域名 当用户输入URL并回车后&#xff0c;浏览器对拿到的URL进行识别&#xff0c;抽取出域名字段&#xff0c;比如https://www.baidu.com,它的域名就是www.baidu.com…

SQL数据库根据需求发送邮件

一、启用数据库邮件 手动启用数据库邮件功能&#xff0c;需执行以下脚本&#xff1a; exec sp_configure show advanced options,1 RECONFIGURE exec sp_configure Database Mail XPs,1 RECONFIGURE With Override 二、邮件服务器设置 1.邮箱启用设置-POP3/IMAP/SMTP/Exch…

DAMA数据管理知识体系指南之数据质量管理

第12章 数据质量管理 12.1 简介 数据质量管理是组织变革管理中一项关键的支撑流程。业务重点的变化、公司的业务整合战略&#xff0c;以及并购与合作&#xff0c;都对IT职能提出了更高要求&#xff0c;包括整合数据源、创建一致的数据副本、交互提供数据或整合数据。与遗留系…

SpringAOP理解实现方式

Aop 什么是Aop&#xff1f; AOP就是面向切面编程&#xff0c;通过预编译方式以及运行期间的动态代理技术来实现程序的统一维护功能。 什么是切面&#xff0c;我理解的切面就是两个方法之间&#xff0c;两个对象之间&#xff0c;两个模块之间就是一个切面。假设在两个模块之间…

9.手动部署Java应用

Jenkins部署Java应用什么java应用手动部署java环境、手动进行代码发布过程1.环境准备配制负载均衡配制webserver&#xff08;tomcat&#xff09;集群本地做域名劫持查看效果2.模拟开发提交Java代码-->推送至gitlab上传代码至gitlab3.运维克隆代码&#xff0c;然后通过maven手…

Yolo-fastestv2训练自己的数据集记录

Yolo-fastestv2训练自己的数据集记录 第一节&#xff1a;代码来源 本机环境&#xff1a;ubuntu20&#xff0c;cuda,cudnn,pytorch1.11.0 代码来源&#xff1a;https://github.com/dog-qiuqiu/Yolo-FastestV2 配置环境后先测试一下环境 终端输入&#xff1a; python3 test.py…

Vue入门介绍

一、背景 目前前端主流框架有Vue、react、Angular等&#xff0c;其中Vue简单易学&#xff0c;只要稍微会点HTML、CSS、JavaScript基础就能很快上手Vue&#xff0c;其门槛低&#xff0c;上手速度快的特点&#xff0c;深受测试开发同学喜爱&#xff0c;已逐渐成为测开必备的前端…

spring回显方式在代码层面的复现(内存马系列篇十四)

前言 在前面的一章中&#xff0c;主要在理论上进行了各种内存马的实现&#xff0c;这里就做为上一篇的补充&#xff0c;自己搭建反序列化的漏洞环境来进行上文中理论上内存马的注入实践。 这是内存马系列文章的第十四篇。 环境搭建 可以使用我用的漏洞环境 https://github…

一款基于java的超级棒的开源支付系统(用来毕设也不错),国内首款开源的互联网支付系统

最近就快要到年末了&#xff0c;小编想着应该会有很多公司开始冲年度的业绩了&#xff0c;既然是冲业绩&#xff0c;就离不开我们的支付系统&#xff0c;所以小编就去网上给大家找到了一款超级棒的开源支付系统&#xff01;帮助大家从头到尾了解清楚这其中的逻辑&#xff01;所…

蓝牙 - 芯片制造商的代号编制以及在Windows上查看

在蓝牙技术的规范中&#xff0c;对很多信息都进行了整理和代号分配&#xff0c;比如生产蓝牙芯片的厂商&#xff0c;也进行了数字编号。 有一个专门的“Assigned Numbers”的PDF文档&#xff0c;记录了蓝牙规范中的各种类型数字所表示的含义。 本文介绍的数字类型&#xff0c…

JavaScript Window - 浏览器对象模型

JavaScript Window - 浏览器对象模型 浏览器对象模型 (BOM) BOM&#xff1a;Browser Object Model 是浏览器对象模型&#xff0c;BOM由多个对象构成&#xff0c;其中代表浏览器窗口的window对象是BOM的顶层对象也是核心对象&#xff0c;其他对象都是该对象的子对象。 BOM对象…

IB-PYP幼儿十大素质培养目标

作为IB候选学校&#xff0c;一直秉承IB教育的核心目标&#xff0c;贯彻在幼儿的学习生活中。IB教育之所以成为当今国际教育的领跑者&#xff0c;最主要的原因是IB教育是切切实实的“全人”教育&#xff0c;“素质”教育&#xff0c;拥有一套完整的教学服务体系。当我们走进IB“…

【机器学习实战】七、梯度下降

梯度下降 一、线性回归 线性回归算法推导过程可以基于最小二乘法直接求解&#xff0c;但这并不是机器学习的思想&#xff0c;由此引入了梯度下降方法。本文讲解其中每一步流程与实验对比分析。 1.初始化 import numpy as np import os %matplotlib inline import matplotli…

C语言(结构和指针)

目录 1.声明结构指针 2.用指针访问成员 3.传递结构成员 4.传递结构的地址 5.传递结构 6.机构的其他特性 7.结构中的字符数组和字符指针 关于为什么要使用指向结构的指针。 第一&#xff0c;就像指向数组的指针比数组本身更容易操作一样&#xff0c;指向结构的指针通常比…

5年自动化测试,终于进字节了,年薪30w其实也并非触不可及

我的职业生涯开始和大多数测试人一样&#xff0c;开始接触都是纯功能界面测试&#xff0c;第一份测试工作就是在电商公司做功能测试&#xff0c;工作忙忙碌碌&#xff0c;每天在各种业务需求学习和点点中度过&#xff0c;过了好几年发现自己还只是一个功能测试工程师&#xff0…