ARM体系结构及接口技术(四)LED灯实验---Makefile文件解析

news2024/9/25 9:40:47

文章目录

  • 一、汇编版本
  • 二、C语言版本
    • (一)Makefile文件
      • 1. `.elf`文件
      • 2. `.map`文件
      • 3. `wildcard`函数
      • 4. `patsubst`函数
    • (二)map.lds
    • (三)start.S

一、汇编版本

# 工程名对应的变量
NAME=asm-led

# 交叉编译器的前缀的变量,不同的交叉编译器的前缀可能不同
CROSS_COMPILE = arm-linux-gnueabihf-

# arm-linux-gnueabihf-gcc : 编译器
CC = $(CROSS_COMPILE)gcc
# arm-linux-gnueabihf-ld : 链接器
LD = $(CROSS_COMPILE)ld
# arm-linux-gnueabihf-objcopy : 格式化拷贝命令
OBJCOPY = $(CROSS_COMPILE)objcopy
# arm-linux-gnueabihf-objdump : 反汇编命令
OBJDUMP = $(CROSS_COMPILE)objdump

# Makefile文件本质是由很多规格构成
# 规则包含:目标,依赖,命令
# 目标:依赖 
# 		(tab键)命令
all:
	@# 将.S文件编译生成.o文件  
	@# -O0 : 代码的优化等级,不优化
	$(CC) -O0 -g -c $(NAME).S -o $(NAME).o
	@# 将.o文件链接生成.elf格式文件
	@# -Ttext:指定程序入口地址,参数为物理地址
	$(LD) -Ttext=0xC0008000 $(NAME).o -o $(NAME).elf
	@# 将.elf文件格式化拷贝生成.bin文件 
	$(OBJCOPY) -O binary $(NAME).elf  $(NAME).bin
	@# 将elf格式文件生成.dis的反汇编文件
	$(OBJDUMP) -D $(NAME).elf > $(NAME).dis
	
clean:
	rm -rf *.elf *.bin *.o *.dis

install:
	sudo cp $(NAME).bin  /mnt/hgfs/share/ 
	@# 拷贝bin文件到共享文件夹中,需要修改为自己的共享文件夹的路径

二、C语言版本

(一)Makefile文件

#CROSS_COMPILE:定义了交叉编译工具链的前缀,
#这里是arm-linux-gnueabihf-,用于编译针对ARM架构的代码。
CROSS_COMPILE = arm-linux-gnueabihf-
#NAME:项目名称,可以在命令行使用NAME参数赋值进行指定 
#如:make NAME=led; 注意此时下载时 make install NAME=led 
NAME = interface
#=============================================================================#
#CFLAGS:定义了C编译器的标志(flags)
#添加gdb调试信息(-g)
#指定ARM架构(-marm)
#显示所有警告(-Wall)
#关闭优化(-O0)选值范围在-O0~-O2,-O2优化等级最高
#指定ABI(-mabi=apcs-gnu)
#启用NEON浮点支持(-mfpu=neon)、指定浮点ABI(-mfloat-abi=softfp)
#禁用内置函数(-fno-builtin)
#不使用标准C库(-nostdinc)
#指定头文件路径(-I)。
CFLAGS += -g -marm -Wall -O0 -mabi=apcs-gnu -mfpu=neon -mfloat-abi=softfp -fno-builtin \
			-nostdinc -I./common/include    -I./include 

#链接命令 		                                       
LD	= $(CROSS_COMPILE)ld
#C编译器
CC	= $(CROSS_COMPILE)gcc
#查看elf文件的符号表信息
NM  = $(CROSS_COMPILE)nm
#二进制格式化拷贝
OBJCOPY = $(CROSS_COMPILE)objcopy
#反汇编工具
OBJDUMP = $(CROSS_COMPILE)objdump
#============================================================================#
#OBJSss:使用wildcard函数收集所有的.S(汇编源文件)和.c(C源文件)文件,这些文件来自不同的目录(start/、common/src/、src/以及当前目录)。	
OBJSss 	:= $(wildcard start/*.S) $(wildcard common/src/*.S) $(wildcard *.S)\
		   $(wildcard start/*.c) $(wildcard common/src/*.c) 			   \
		   $(wildcard src/*.c) $(wildcard *.c) 
#OBJSs:将.S文件(汇编源文件)通过patsubst函数转换成.o(目标文件)的形式。
#patsubst 函数被用来将.S 文件(汇编源文件)的列表转换成对应的 .o 文件(目标文件)的列表
OBJSs  	:= $(patsubst %.S,%.o,$(OBJSss))
#OBJS:将OBJSs变量中所有的.c文件(C源文件)也转换成.o(目标文件)的形式。
OBJS 	:= $(patsubst %.c,%.o,$(OBJSs))
#============================================================================#
#$@:所有的目标
#$<:第一个依赖文件
%.o: %.S 
	@echo "  AS      $@"
	@$(CC) $(CFLAGS) -c -o  $@ $<
%.o: %.c
	@echo "  CC      $@"
	@$(CC) $(CFLAGS) -c -o  $@ $<

#目标all依赖于规则clean和$(OBJ)的文件,因此当执行make时,会先执行clean目标,然后编译所有源文件生成.o文件
all:clean  $(OBJS)
	#使用链接器$(LD)链接这些.o文件生成.elf文件
	#-T map.lds 按照map.lds链接脚本文件进行链接
	@echo "  LD      Linking $(NAME).elf"
	@$(LD)  $(OBJS) -T map.lds -o $(NAME).elf
	#接着使用$(OBJCOPY)将.elf文件转换成.bin文件
	@echo "  OBJCOPY Objcopying $(NAME).bin"
	@$(OBJCOPY)  -O binary  $(NAME).elf $(NAME).bin 
	#使用$(NM)生成符号表(.map文件)
	@echo "  MAP     Generating $(NAME).map"
	@$(NM) $(NAME).elf > $(NAME).map 
	#最后使用$(OBJDUMP)生成反汇编代码(.dis文件)
	@echo "  OBJDUMP Objdumping $(NAME).dis"
	@$(OBJDUMP) -DS $(NAME).elf > $(NAME).dis 

#distclean和clean:一个规则可以有多个目标,这两个目标有相同的依赖文件,
#它们都会删除所有的.o文件、.elf文件、.bin文件、.dis文件和.map文件,并打印“CLEAN complete.”消息。
distclean clean:
	@rm -rf $(OBJS) *.elf *.bin *.dis *.map
	@echo "  CLEAN   complete."

install:
	sudo cp $(NAME).bin /mnt/hgfs/Desktop/

执行make时的输出:
在这里插入图片描述

1. .elf文件

.elf文件,Executable and Linkable Format(可执行和可链接格式)的缩写,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储等文件的格式。ELF文件主要用于Linux平台,是Linux下目标文件和可执行文件的标准格式。

objcopy 在将 .elf 文件转换成 .bin 文件时,主要执行的操作是提取 .elf 文件中的二进制代码段(即机器码),并将这些代码段直接写入到 .bin 文件中,而不包含 .elf 文件中的其他部分,如符号表、重定位信息、调试信息等。

2. .map文件

.map文件提供关于编译后程序(如可执行文件或库)的符号表、内存布局、段分配等详细信息的视图,是开发者在编译和调试过程中非常有用的工具,它提供了关于程序内存布局和符号表的详细信息,有助于开发者理解程序的行为、查找问题并进行优化。

3. wildcard函数

objects := $(wildcard *.o) 让 objects 的值是所有[.o]的文件名的集合

wildcard函数支持在不同路径下搜索文件,并将搜索到的所有符合条件的值的集合返回

4. patsubst函数

$(patsubst <pattern>,<replacement>,<text>)
  • 名称:模式字符串替换函数----patsubst。
  • 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符
    合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。 (可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
  • 返回:函数返回被替换过后的字符串。

OBJS := $(patsubst %.c,%.o,$(OBJSs))
此处patsubst函数会将$(OBJSs)中所有以.c为后缀的文件名的字符串替换为.o为后缀的文件名的字符串。另一句同理
执行完成后OBJS变量中存放的就是所有.c和.S文件更改为.o后缀的文件名的字符串

(二)map.lds

链接脚本(Linker Script)是用于指定如何将程序的各个部分(如代码、数据等)放置到内存中的。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
/*OUTPUT_FORMAT:指定输出文件的格式。这里设置为elf32-littlearm,表示输出文件为32位的小端ARM ELF格式*/

OUTPUT_ARCH(arm)
/*OUTPUT_ARCH:指定输出文件的架构,这里是arm。*/

ENTRY(_start)
/*ENTRY:指定程序的入口点,这里是_start,这通常是C语言程序的第一个执行指令所在的位置。*/

SECTIONS /*SECTIONS部分定义了如何将程序的各个段(section)映射到内存中。*/
{
	. = 0xc0008000;  /*程序的入口地址*/
	/*. = 0xc0008000;:设置当前位置(.)为0xc0008000,这通常是程序的加载地址,也是.text段的开始地址。*/
	
	. = ALIGN(4);    /*对齐*/
	/*. = ALIGN(4);:确保接下来的段从4字节对齐的地址开始。这是为了确保符合ARM架构的对齐要求,提高访问效率。*/
	
	.text :     /*.text段包含了程序的可执行代码,代码段*/
	{
		start/start.o(.text)  
		/*第一个.o文件,start/start.o(.text):特别指定了start.o文件中的.text段应该首先被包含进来。这通常用于确保启动代码(如设置堆栈、初始化硬件等)首先被执行。*/
		*(.text)   /**(.text):随后包含所有其他.o文件中的.text段。具体位置,由编译器决定*/
	}
	. = ALIGN(4);
	
    .rodata :     /*只读数据端,.rodata段包含了只读数据,如字符串常量等。*/
	{ *(.rodata) } /* *(.rodata):包含所有.o文件中的.rodata段。 */
    . = ALIGN(4);
    
    .data :      /*初始化的全局变量,.data段包含了已初始化的全局变量。
*(.data):包含所有.o文件中的.data段。*/
	{ *(.data) }
    . = ALIGN(4);
    
	__bss_start = .; /*__bss_start = . 定义了一个符号__bss_start,其值为.bss段的开始地址。这可以用于在程序运行时定位.bss段的起始位置。*/
    .bss :      /*.bss段用于存储未初始化的全局变量。在内存中,.bss段通常不占用实际的空间(即不存储具体的值),但在加载时,会为其分配空间并清零。*/
     { *(.bss) } /**(.bss):包含所有.o文件中的.bss段。*/
	__bss_end__ = .;  /*__bss_end__ = . 定义了另一个符号__bss_end__,其值为.bss段的结束地址。这可以用于计算.bss段的大小或在程序运行时进行其他操作。*/
}

(三)start.S

.text /*这是一个汇编指令,用于指示接下来的代码段应该被放置在程序的文本(即代码)段中。在链接过程中,.text段通常会被放置在可执行文件的开始部分。*/

	.global	_start /*这条指令声明了一个全局符号_start,这是程序的入口点。在链接时,链接器会查找这个符号,并将其地址设置为程序执行时的初始PC(程序计数器)值。*/
_start: /*这是程序的入口点标签。*/
@ 异常向量表
	b reset
	ldr pc, _undefined_instruction
	ldr pc, _software_interrupt
	ldr pc, _prefetch_abort
	ldr pc, _data_abort
	ldr pc, _not_used
	ldr pc, _irq
	ldr pc, _fiq

_undefined_instruction:
	.word undefined_instruction
_software_interrupt:
	.word software_interrupt
_prefetch_abort:
	.word prefetch_abort
_data_abort:
	.word data_abort
_not_used:
	.word not_used
_irq:
	.word irq
_fiq:
	.word fiq
/*这些标签后面跟随的.word指令用于定义这些标签对应的内存地址中存储的值,即异常处理程序的地址。例如,_undefined_instruction: .word undefined_instruction表示在_undefined_instruction标签对应的内存地址中存储undefined_instruction(一个标签或地址,指向未定义指令异常的处理程序)的地址。*/

 /* The actual reset code */
reset:
	@ 重新映射异常向量表的入口地址
	/* Set Vector Base Address Register */
	mrc p15, 0, r0, c1, c0, 0
	bic r0, #(1<<13)
	mcr p15, 0, r0, c1, c0, 0
	ldr	r0,=0xc0008000
	mcr	p15,0,r0,c12,c0,0		@ Vector Base Address Register

	/* Set the cpu to svc32 mode */
	mrs r0, cpsr
	bic r0, r0, #0x1f
	orr r0, r0, #0xd3
	msr cpsr, r0

	/* Enable NEON/VFP unit */
	mrc p15, #0, r1, c1, c0, #2
	orr r1, r1, #(0xf << 20)
	mcr p15, #0, r1, c1, c0, #2
	mov r1, #0
	mcr p15, #0, r1, c7, c5, #4
	mov r0, #0x40000000
	fmxr fpexc, r0

	/* Cache init */
	mrc	p15, 0, r0, c0, c0, 0
	and	r1, r0, #0x00f00000
	and	r2, r0, #0x0000000f
	orr r2, r2, r1, lsr #20-4
	cmp r2, #0x30
	mrceq p15, 0, r0, c1, c0, 1
	orreq r0, r0, #0x6
	mcreq p15, 0, r0, c1, c0, 1

	/* Invalidate L1 I/D */
	mov r0, #0
	mcr	p15, 0, r0, c8, c7, 0
	mcr	p15, 0, r0, c7, c5, 0

	/* Disable mmu stuff and caches */
	mrc p15, 0, r0, c1, c0, 0
	bic r0, r0, #0x00002000
	bic r0, r0, #0x00000007
	orr r0, r0, #0x00001000
	orr r0, r0, #0x00000002
	orr r0, r0, #0x00000800
	mcr p15, 0, r0, c1, c0, 0

	/* Initialize stacks */
	@ 初始化各种模式下的占空间
init_stack:
	ldr	r0, stacktop        /*get stack top pointer*/

	/********svc mode stack********/
	mov	sp, r0
	sub	r0, #128*4          /*512 byte  for irq mode of stack*/
	/********irq mode stack********/
	msr	cpsr, #0xd2
	mov	sp, r0
	sub	r0, #128*4          /*512 byte  for fiq mode of stack*/
	/********fiq mode stack********/
	msr	cpsr, #0xd1
	mov	sp, r0
	sub	r0, #0
	/********abort mode stack******/
	msr	cpsr, #0xd7
	mov	sp, r0
	sub	r0, #0
	/********undefine mode stack**/
	msr	cpsr, #0xdb
	mov	sp, r0
	sub	r0, #0
    /***sys mode and usr mode stack***/
	msr	cpsr, #0x10
	mov	sp, r0             /*1024 byte  for user mode of stack*/

    /******clear bss section********/
	@ 清除BSS段
	ldr	r0, =__bss_start	/* this is auto-relocated! */
	ldr	r1, =__bss_end__	/* this is auto-relocated! */
	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l: 
	cmp r0, r1			/* while not at end of BSS */
	strlo r2, [r0]			/* clear 32-bit BSS word */
	addlo r0, r0, #4		/* move to next */
	blo	clbss_l

	/* Call _main */
	ldr pc, =main     @ 汇编调用C  跳转到main.c文件的main函数中
/*
 * Exception handlers
 */
	.align 5  // 2的5次方,=32bit 也就是4字节对其
undefined_instruction:
	b	.

	.align 5
software_interrupt:
	b	.

	.align 5
prefetch_abort:
	b	.

	.align 5
data_abort:
	b	.

	.align 5
not_used:
	b	.

	.align 5
	.global irq
irq:
	sub  lr, lr, #4
	stmfd sp!, {r0-r12, lr}
	bl do_irq
	ldmfd sp!, {r0-r12, pc}^

	.align 5
	.global fiq
fiq:
	b .
stacktop:    .word 		stack + 4 * 512
.data

stack:	 .space  4 * 512

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

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

相关文章

十、软件工程基础知识(考点篇)试题

降低需求变更成本&#xff0c;第一想到的就是原型法&#xff0c;后面对于已完成开发工作的的反馈意见&#xff0c;已完成开发工作只有增量式的吧&#xff0c;先开发核心的&#xff0c;然后发布一版&#xff0c;得到用户反馈再修改并开发次核心。快速原型强调的是&#xff0c;先…

BP神经网络学习内容分享:数据降维

在数据分析和机器学习的领域中&#xff0c;数据降维是一项非常重要的技术。它旨在减少数据集中的特征数量&#xff0c;同时尽可能保留原始数据的重要信息。这不仅有助于减少计算复杂度和提高算法效率&#xff0c;还能有效避免过拟合&#xff0c;提升模型的泛化能力。本文将简要…

数学建模--皮尔逊相关系数、斯皮尔曼相关系数

目录 1.总体的皮尔逊相关系数 2.样本的皮尔逊相关系数 3.对于皮尔逊相关系数的认识 4.描述性统计以及corr函数 ​编辑 5.数据导入实际操作 6.引入假设性检验 6.1简单认识 6.2具体步骤 7.p值判断法 8.检验正态分布 8.1jb检验 8.2威尔克检验&#xff1a;针对于p值进行…

【单片机原理及应用】实验:数字秒表显示器

目录 一、实验目的 二、实验内容 三、实验步骤 四、记录与处理 五、思考 六、成果文件提取链接 一、实验目的 熟悉中断和定时/计数器工作原理&#xff0c;掌握定时器的C51编程与调试方法。 二、实验内容 【参照图表】 图A.6 &#xff08;1&#xff09;创建一个包含80C51固…

【OWOD论文】开放世界中OD代码_2_模型部分

简介 本文记录OWOD代码中的模型代码部分。数据部分可看我上一个博客【【OWOD论文】开放世界中OD代码_1_数据部分-CSDN博客】 模型代码 1 起步 在代码中找到 detectron2\engine\defaults.py DefaultTrainer类 __init__方法 根据上述 build_model 回溯到 detectron2\modeling\…

OCC笔记:Windows下OCC的编译

一、源码下载 进OCC官网下载https://dev.opencascade.org/release即可&#xff0c;或直接Clone它的Git库https://dev.opencascade.org/resources/git_repository&#xff0c;本文用的源码库版本为7.4.0&#xff08;我本机安装的VS2013&#xff0c;我又想用到AIS_ViewCube&…

使用Blender云渲染的好处是什么?

​Blender是一款功能强大的开源3D创作软件&#xff0c;用于包括建模、动画、仿真、渲染、合成和视频编辑在内的多种应用。然而&#xff0c;Blender的渲染过程有时可能非常耗费资源&#xff0c;特别是处理复杂的3D场景时。作为CG行业不可或缺的一部分&#xff0c;云渲染通过使用…

chat2DB体验

文章目录 Chat2DB体验的印象Chat2DB是什么&#xff1f;流水帐数据库示例新建数据表生成测试数据查询数据特殊查询 Chat2DB 体验的印象 主页是https://chat2db-ai.com/ 因为最近物理研究需要用到很多数据&#xff0c;所以试用了一个号称神级AI数据库系统。 首先&#xff0c; …

【论文解析】基于脉动阵列的层融合注意力模型加速器结构

作者及发刊详情 刘晓航, 姜晶菲, 许金伟. 基于脉动阵列的层融合注意力模型加速器结构[J]. Computer Engineering & Science/Jisuanji Gongcheng yu Kexue, 2023, 45(5). 摘要 正文 主要工作贡献 1&#xff09;)提出了硬件协同控制的注意力机制矩阵分块方法 2&#xf…

数据仓库系列14:数据清洗和转换的常见方法有哪些?

数据仓库的建立不仅仅是数据的简单存储&#xff0c;更是对数据的深度利用。而数据清洗和转换是确保数据质量和一致性的重要环节。在这篇文章中&#xff0c;我们将深入探讨数据清洗和转换的常见方法&#xff0c;帮助你在数据仓库中更高效地处理数据。 目录 为什么数据清洗和转换…

任务通知笔记

1、任务通知简介 任务通知 用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 任务通知与队列、信号量和时间标志组的区别 任务通知的优势及劣势 优势 效率更高&#xff1a;使用任务通知向任务发送事件或者数据比使用队列、事件标志…

C语言典型例题56

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 例题4.8 将范围为100~200的不能被3整除的数输出。 代码&#xff1a; //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //例题4.8 将范围为100~200的不能被3整除的数输出。//#include <stdio.h>…

您下一款项目管理工具何必是它,10款软件推荐

国内外主流的 10 款项目管理系统对比&#xff1a;PingCode、Worktile、Teambition、明道云、泛微E-cology、Asana、Trello、Monday.com、ClickUp、Wrike。 在项目管理的世界里&#xff0c;选择合适的管理工具似乎是一个令人头疼的问题。你是否经常在众多选项中感到迷茫&#xf…

AI如何改变科学与数学领域:陶哲轩演讲解析

引言 在当今技术迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;正逐步渗透到各个领域&#xff0c;其对科学与数学领域的影响尤为引人关注。菲尔茨奖获得者陶哲轩最近在一场演讲中深刻探讨了AI在科学与数学中的应用及其潜在的革命性影响。本文将基于陶哲轩的演…

Goby 漏洞发布|Nacos Jraft 服务文件读取漏洞【已复现】

漏洞名称&#xff1a;Nacos Jraft 服务文件读取漏洞 English Name&#xff1a;Nacos Jraft Services File Read Vulnerability CVSS core: 5.0 漏洞描述&#xff1a; NACOS 是阿里巴巴推出来的一个新开源项目&#xff0c;是一个更易于构建云原生应用的动态服务发现、配置管…

J.U.C Review - Java线程间的通信

文章目录 Java线程间的通信无锁的程序锁与同步等待/通知机制信号量管道 其它通信相关join方法join 方法概述底层实现细节小结 sleep方法1. Thread.sleep 方法详解2. sleep 和 wait 的主要区别3. 实际代码示例 ThreadLocal类ThreadLocal 的基本概念ThreadLocal 的主要方法Thread…

STM32——看门狗(独立/窗口)

程序运行的保障措施&#xff0c;需要在程序中定期喂狗实现&#xff0c;如果某次没有喂&#xff0c;表示程序出现卡死或者其他状态&#xff0c;此时看门狗就会自动复位电路&#xff0c;防止程序长时间卡死。相当于自动复位电路。 独立看门狗&#xff1a;有单独的时钟LSI 窗口看…

谷歌发布新AI GameNGen:AI也能实时生成游戏画面!

有关 GameNGen 的帖子 又有一则消息直接让全网为之狂欢&#xff01;Google 推出了一个实时AI生成的游戏引擎 GameNGen。目前 GameNGen 生成3D游戏的祖宗《BOOM》的视频已经火遍 X 平台&#xff0c;在视频中&#xff0c;游戏画面每一个画面都是由AI实时生成&#xff0c;可以说是…

应用层协议(下)Https加密Http的秘密(含逻辑图解 简单易学 通俗易懂!)

绪论​ “如今我努力奔跑&#xff0c;不过是为了追上那个曾经被寄予厚望的自己 —— 约翰丶利文斯顿”&#xff0c;本章承接上章Http&#xff0c;没看过强烈建议看后再看本章&#xff0c;本章主要就是学习Https是干什么的并且去底层的学习Http的原理&#xff0c;将会讲到Https的…

pdf转dwg怎么转换?5个软件教你轻松转换文件

pdf转dwg怎么转换&#xff1f;5个软件教你轻松转换文件 将PDF文件转换为DWG格式可以帮助你将静态的图像和矢量图形转换为可编辑的CAD图纸。这在建筑、工程和设计领域尤为重要&#xff0c;因为它可以让你在CAD软件中进一步编辑和利用这些图纸数据。以下是五款能够帮助你轻松将P…