




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
本学期考核方法本学期平时分占60%,期末考试占40%小测试共2次,每次6分,共20分作业加考勤基础分20分,少一次扣4分,作业不交加考勤不到场超过5次将取消考试资格。实验课有两个实验,每次10分,共20各种加分,回答问题,实验提前做完等。期末考试形式为闭卷考试
第四章程序的链接
目标文件格式
符号解析与重定位
共享库与动态链接可执行文件的链接生成主要教学目标使学生了解链接器是如何工作的,从而能够养成良好的程序设计习惯,并增加程序调试能力。通过了解可执行文件的存储器映像来进一步深入理解进程的虚拟地址空间的概念。包括以下内容链接和静态链接概念三种目标文件格式符号及符号表、符号解析使用静态库链接重定位信息及重定位过程可执行文件的存储器映像可执行文件的加载共享(动态)库链接程序的链接分以下三个部分介绍第一讲:目标文件格式程序的链接概述、链接的意义与过程ELF目标文件、重定位目标文件格式、可执行目标文件格式第二讲:符号解析与重定位符号和符号表、符号解析与静态库的链接重定位信息第三讲:动态链接动态链接的特性、程序加载时的动态链接、程序运行时的动态链接一个典型程序的转换处理过程1#include<stdio.h>23intmain()4{5printf("hello,world\n");6}经典的“hello.c”C-源程序#include<sp><stdio.3510511099108117100101326011511610010511146h>\n\nint<sp>main()\n{1046210101051101163210997105110404110123\n<sp><sp><sp><sp>printf("hel10323232321121141051101161024034104101108lo,<sp>world\n");\n}10811144321191111141081009211034415910125hello.c的ASCII文本表示计算机能够直接识别hello.c源程序吗?不能,需要转换为机器语言代码!即:编译、汇编等功能:输出“hello,world”一个C语言程序举例intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cswap.cexternintbuf[];
int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}(1)预处理(cpp)。在高级语言源程序中插入所有用#include命令指定的文件和用#define声明指定的宏。(2)编译(cc1)。将预处理后的源程序文件编译生成相应的汇编语言程序。(3)汇编(as)。由汇编程序将汇编语言源程序文件转换为可重定位目标文件。(4)链接(ld)。由链接器将多个可重定位目标文件及库例程(如printf.o)链接起来,生成可执行文件。可执行文件的生成使用GCC编译器编译并链接生成可执行程序P:unix>gcc-O2-g-opmain.cswap.cunix>./pLinker(ld)Translators(cpp,cc1,as)main.cmain.oTranslators(cpp,cc1,as)swap.cswap.opSourcefilesSeparatelycompiledrelocatableobjectfilesFullylinkedexecutableobjectfile(containscodeanddataforallfunctionsdefinedinmain.candswap.c)GCC编译器的静态链接过程引用符号的地址需要重定位main()main.oint*bufp0=&buf[0]swap()swap.oSystemcodeint
buf[2]={1,2}System
data可重定位目标文件可执行目标文件.text.data.text.data.text.dataintbuf[2]={1,2}Headersmain()swap()0int
*bufp0=&buf[0]Moresystem
codeSystem
data.text.symtab.debug.dataint*bufp1.bssSystem
codestaticint*bufp1.bss虽然是swap的本地符号,也需在.bss节重定位使用链接的优点链接带来的好处1:模块化(1)一个程序可以分成很多源程序文件(2)可构建公共函数库,如数学库,标准C库等链接带来的好处2:效率高(1)时间上,可分开编译只需重新编译修改的源程序文件,然后重新链接(2)空间上,无需包含共享库所有代码
源文件中无需包含共享库函数的源码,只要直接调用即可可执行文件和运行时的内存中只需包含所调用函数的代码而不需要包含整个共享库链接操作的步骤Step1.符号解析(Symbolresolution)程序中有定义和引用的符号(包括变量和函数等)voidswap(){…}/*定义符号swap*/swap();/*引用符号swap*/int*xp=&x;/*定义符号xp,引用符号x*/编译器将定义的符号存放在一个符号表(symboltable)中.符号表是一个结构数组每个表项包含符号名、长度和位置等信息链接器将每个符号的引用都与一个确定的符号定义建立关联Step2.重定位将多个代码段与数据段分别合并为一个单独的代码段和数据段将.o文件中每个符号的相对位置重定位为可执行文件中的绝对存储位置将原来符号表中的位置信息修改为重定位后的位置信息三类目标文件
可重定位目标文件(.ofile)其代码和数据可和其他可重定位文件合并为可执行文件每个.o文件由对应的.c文件生成每个.o文件代码和数据地址都从0开始可执行目标文件(.afile)包含的代码和数据可以被直接复制到内存并被执行代码和数据地址为虚拟地址空间中的地址共享的目标文件(.sofile)特殊的可重定位目标文件,能在装入或运行时被装入到内存并自动被链接Windows中称其为DynamicLinkLibraries(DLLs)
00000000<add>: 0:55 push%ebp 1:89e5mov%esp,%ebp 3:83ec10sub$0x10,%esp 6:8b450cmov0xc(%ebp),%eax 9:8b5508mov0x8(%ebp),%edx c:8d0402lea(%edx,%eax,1),%eax f:8945fcmov%eax,-0x4(%ebp) 12:8b45fcmov-0x4(%ebp),%eax 15:c9leave 16:c3ret080483d4<add>:80483d4:55push%ebp80483d5:89e5mov%esp,%ebp80483d7:83ec10sub$0x10,%esp80483da:8b450cmov0xc(%ebp),%eax80483dd:8b5508mov0x8(%ebp),%edx80483e0:8d0402lea(%edx,%eax,1),%eax80483e3:8945fcmov%eax,-0x4(%ebp)80483e6:8b45fcmov-0x4(%ebp),%eax80483e9:c9leave80483ea:c3retobjdump-dtest.o
objdump-dtest
/*main.c*/intadd(int,int);intmain(){returnadd(20,13);}/*test.c*/intadd(inti,intj){intx=i+j;returnx;}ExecutableandLinkableFormat(ELF)两种视图链接视图:Relocatableobjectfiles执行视图:Executableobjectfiles节(section)是ELF文件中具有相同特征的最小可处理单位
.text节:代码.data节:数据.rodata:只读数据.bss:未初始化数据由不同的段(segment)组成,描述节如何映射到存储段中,可多个节映射到同一段,如:可合并.data节和.bss节,并映射到一个可读可写数据段中
链接视图执行视图可重定位目标文件格式ELF头占16字节,包括字长、字节序(大端/小端)、文件类型(.o,exec,.so)、机器类型(如IA-32)、节头表的偏移、节头表的表项大小及表项个数.text节编译后的代码部分.rodata节只读数据,如printf格式串、switch跳转表等.data节已初始化的全局变量.bss节未初始化全局变量,仅是占位符,不占据任何实际磁盘空间。目标文件格式区分初始化和非初始化是为了空间效率ELFheader.textsection.rodatasection.bsssection.symtabsection.rel.txtsection.rel.datasection.debugsectionSectionheadertable0.datasection.strtabsection.linesection可重定位目标文件格式.symtab节存放函数和全局变量(符号表)信息,它不包括局部变量条目.rel.text节.text节的重定位信息,用于重新修改代码段的指令中的地址信息.rel.data节.data节的重定位信息,用于对被模块使用或定义的全局变量进行重定位的信息.debug节调试用符号表(gcc-g)strtab节包含symtab和debug节中符号及节名Sectionheadertable(节头表)每个节的节名、偏移和大小ELFheader.textsection.rodatasection.bsssection.symtabsection.rel.txtsection.rel.datasection.debugsectionSectionheadertable0.datasection.strtabsection.linesection可执行目标文件格式与.o文件稍有不同:ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段为0多一个.init节,用于定义_init函数,该函数用来进行可执行目标文件开始执行时的初始化工作少两.rel节(无需重定位)多一个程序头表,也称段头表(segmentheadertable),是一个结构数组可执行文件的存储器映像0%esp(栈顶)brk0xC000000000x08048000KernelvirtualmemoryMemory-mappedregionforshared
librariesRun-timeheap(createdby
malloc)Userstack(created
at
runtime)Unused0Read/write
segment(.data,.bss)Read-onlysegment(.init,.text,.rodata)从可执行文件装入程序(段)头表描述如何映射ELFheaderSegmentheadertable.textsection.datasection.bsssection.symtab.debug.rodatasection.line.initsection.strtab可执行文件中的程序头表typedefstruct{Elf32_Wordp_type;Elf32_Offp_offset;Elf32_Addrp_vaddr;Elf32_Addrp_paddr;Elf32_Wordp_filesz;Elf32_Wordp_memsz;Elf32_Wordp_flags;Elf32_Wordp_align;}Elf32_Phdr;程序头表能够描述可执行文件中的节与虚拟空间中的存储段之间的映射关系一个表项说明虚拟地址空间中一个连续的片段或一个特殊的节
以下是GNUREADELF显示的某可执行目标文件的程序头表信息可执行文件中的程序头表程序头表中有8个表项,其中有两个是可装入段(type=LOAD)对应表项。第一可装入段对应第0x00000~0x004d3字节(包括ELF头、程序头表、.init、.text和.rodata节),映射到虚拟地址0x8048000开始长度为0x4d4字节的区域,按0x1000=212=4K字节对齐,具有只读/执行权限(Flg=RE),是只读代码段(read-onlycode)。第二可装入段对应第0x000f0c开始长度为0x108字节的.data节,映射到虚拟地址0x8049f0c开始的长度为0x110字节的存储区域,在0x110=272字节的存储区中,前0x108=264字节用.data节内容初始化,而后面272-264=8个字节对应.bss节,初始化为0,该段按0x1000=4KB对齐,具有可读可写权限(Flg=RW),因此,它是一个可读写数据段(read/writedatasegment)。程序的链接分以下三个部分介绍第一讲:目标文件格式程序的链接概述、链接的意义与过程ELF目标文件、重定位目标文件格式、可执行目标文件格式第二讲:符号解析与重定位符号和符号表、符号解析与静态库的链接重定位信息第三讲:动态链接动态链接的特性、程序加载时的动态链接、程序运行时的动态链接符号和符号解析
每个可重定位目标模块m都有一个符号表,它包含了在m中定义和引用的所有符号。有三种链接器符号:Globalsymbols(模块内部定义的全局符号)由模块m定义并能被其他模块引用的符号。例如,非staticC函数和非static的C全局变量(指不带static的全局变量)
如,main.c中的全局变量名bufExternalsymbols(外部定义的全局符号)由其他模块定义并被模块m引用的全局符号
如,main.c中的函数名swapLocalsymbols(本模块的局部符号)仅由模块m定义和引用的本地符号。例如,在模块m中定义的带static的C函数和全局变量如,swap.c中的static变量名bufp1链接器局部符号不是指程序中的局部变量(分配在栈中的临时性变量),链接器不关心这种局部变量符号和符号解析intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cexternintbuf[];
int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}swap.cGlobalExternalExternalLocalGlobal局部变量Global目标文件中的符号表符号表(symtab)中每个条目的结构如下typedefstruct{intname;/*指向符号对应字符串在strtab节中的偏移*/ intvalue;/*在对应section中的偏移量,可执行文件中是虚拟地址*/intsize;/*符号对应目标所占字节数*/chartype:4,/*符号对应目标的类型:数据、函数、源文件、节*/binding:4;/*符号对应目标是全局符号还是局部符号*/charreserved;charsection;/*符号对应目标所在的section,或其他情况*/}Elf_Symbol;其他情况:ABS表示不该被重定位;UND表示未定义;COM表示未初始化数据(.bss),此时,value表示对齐要求,size给出最小大小目标文件中的符号表main.o中的符号表中最后三个条目Num: value Size Type Bind Ot Ndx Name8: 0 8 Data Global 0 3 buf9: 0 33 Func Global 0 1 main10: 0 0 Notype Global 0 UND swapswap.o中的符号表中最后4个条目Num: value Size Type Bind Ot Ndx Name8: 0 4 Data Global0 3 bufp09: 0 0 NotypeGlobal0 UND buf10: 0 36 Func Global 0 1 swap11: 4 4 Data Local 0 COM bufp1buf是main.o中第3节(.data)偏移为0的符号,是全局变量,占8B;main是第1节(.text)偏移为0的符号,是全局函数,占33B;swap是main.o中未定义的符号,不知道类型和大小,全局的(在其他模块定义)bufp1是未分配地址且未初始化的本地变量(ndx=COM),按4B对齐且占4B符号解析目的:将每个模块中引用的符号与某个目标模块中的定义符号建立关联。每个定义符号在代码段或数据段中都被分配了存储空间,将引用符号与对应定义符号建立关联后,就可在重定位时将引用符号的地址重定位为相关联的定义符号的地址。本地符号在本模块内定义并引用,因此,其解析较简单,只要与本模块内唯一的定义符号关联即可。全局符号(外部定义的、内部定义的)的解析涉及多个模块,故较复杂
“符号的定义”其实质是什么?是指符号被分配了虚拟地址空间。符号为函数名即指其代码所在区;符号为变量即指其占的静态数据区。全局符号的符号解析全局符号的强/弱特性函数名和已初始化的全局变量名是强符号未初始化的全局变量名是弱符号
intfoo=5;p1(){……}intfoo;p2(){……}p1.cp2.cstrongweakstrongstrong以下符号哪些是强符号?哪些是弱符号?全局符号的符号解析intbuf[2]={1,2};voidswap();intmain(){swap();return0;}main.cexternintbuf[];
int*bufp0=&buf[0];staticint*bufp1;voidswap(){inttemp;bufp1=&buf[1];temp=*bufp0;*bufp0=*bufp1;*bufp1=temp;}swap.c强符号此处为引用弱符号本地符号强符号局部变量强符号以下符号哪些是强符号?哪些是弱符号?链接器对符号的解析规则多重定义符号的处理规则
Rule1:强符号不能多次定义强符号只能被定义一次,否则链接错误
Rule2:若一个符号被定义为一次强符号和多次弱符号,则按强定义为准对弱符号的引用被解析为其强定义符号
Rule3:若有多个弱符号定义,则任选其中一个使用命令gcc–fno-common链接时,会告诉链接器在遇到多个弱定义的全局符号时输出一条警告信息。
多重定义符号的解析举例intx=10;intp1(void);intmain(){x=p1();returnx;}main.cintx=20;intp1(){returnx;}p1.cmain只有一次强定义p1有一次强定义,一次弱定义x有两次强定义,所以,链接器将输出一条出错信息
以下程序会发生链接出错吗?多重定义符号的解析举例p1.cy一次强定义,一次弱定义z两次弱定义p1一次强定义,一次弱定义main一次强定义#include<stdio.h>inty=100;intz;voidp1(void);intmain(){z=1000;p1();printf(“y=%d,z=%d\n”,y,z);return0;}main.cinty;intz;voidp1(){y=200;z=2000;}问题:打印结果是什么?y=200,z=2000以下程序会发生链接出错吗?该例说明:在两个不同模块定义相同变量名,很可能发生意想不到的结果!多重定义符号的解析举例p1.c该例说明:两个重复定义的变量具有不同类型时,更容易出现难以理解的结果!
main.c问题:打印结果是什么?d=0,x=1072693248
以下程序会发生链接出错吗?1#include<stdio.h>2intd=100;3intx=200;4voidp1(void);5intmain()6{7p1();8printf(“d=%d,x=%d\n”,d,x);9return0;10}1doubled;23voidp1()4{5d=1.0;6}p1执行后d和x处内容是什么?FLD1FSTPl&d1.0:0011111111110…0B=3FF0000000000000H多重定义符号的解析举例打印结果:d=0,x=1072693248Why?
1doubled;23voidp1()4{5d=1.0;6}…….1intd=100;2intx=200;3intmain()4{5p1();6printf(“d=%d,x=%d\n”,d,x);7return0;8}main.cp1.c理解该问题需要知道:机器级数据的表示与存储链接器的符号解析规则……double型数1.0对应的机器数3FF0000000000000H
低高IA-32是小端方式多重定义全局符号的问题尽量避免使用全局变量一定需要用的话,就按以下规则使用尽量使用本地变量(static)全局变量要赋初值外部全局变量要使用extern多重定义全局变量会造成一些意想不到的错误,而且是默默发生的,编译系统不会警告,并会在程序执行很久后才能表现出来,且远离错误引发处。特别是在一个具有几百个模块的大型软件中,这类错误很难修正。大部分程序员并不了解链接器如何工作,因而养成良好的编程习惯是非常重要的。头文件(.h文件)的作用#include"global.h"intf(){returng+1;}c1.cglobal.h#ifdefINITIALIZEintg=23;staticintinit=1;#elseintg;staticintinit=0;#endif#include<stdio.h>#include"global.h"intmain(){if(!init)g=37;intt=f();printf("Callingfyields%d\n",t);return0;}c2.c预处理操作#include"global.h"intf(){returng+1;}c1.cglobal.h#ifdefINITIALIZE
intg=23;staticintinit=1;#else
intg;staticintinit=0;#endifintg=23;staticintinit=1;intf(){returng+1;}intg;staticintinit=0;intf(){returng+1;}定义INITIALIZE没有定义INITIALIZE#include指示被执行,插入.h文件的内容到源文件中如何划分模块?许多函数无需自己写,可使用共享库函数Math,I/O,memorymanagement,stringmanipulation,etc.避免以下两种极端做法将所有函数都放在一个源文件中修改一个函数需要对所有函数重新编译时间和空间两方面的效率都不高一个函数放在一个源文件中需要程序员显式地进行链接效率高,但模块太多,故太繁琐静态共享库静态库(.aarchivefiles)将所有相关的目标模块打包为一个单独的文件,称为静态库文件,也称为存档文件(archive)增强链接器功能,使其能通过查找一个或多个库文件中的符号来解析符号在构建可执行文件时只需指定库文件名,链接器会自动到库中寻找那些应用程序用到的目标模块,并且只把用到的模块从库中拷贝出来在gcc命令行中无需明显指定C标准库libc.a(默认库)静态库的创建Translatoratoi.catoi.oTranslatorprintf.cprintf.olibc.aArchiver(ar)...Translatorrandom.crandom.ounix>arrslibc.a\atoi.oprintf.o…random.oCstandardlibraryArchiver(归档器)允许增量更新,只要重新编译需修改的源码并将其.o文件替换到静态库中。自定义一个静态库文件#include<stdio.h>voidmyfunc1(){printf("%s","Thisismyfunc1!\n");}#include<stdio.h>voidmyfunc2(){printf("%s","Thisismyfunc2\n");}gcc–cmyproc1.cmyproc2.carrcsmylib.amyproc1.omyproc2.omyproc1.cmyproc2.c链接器中符号解析的全过程
voidmyfunc1(viod);intmain(){myfunc1();return0;}main.c调用关系:main→myfunc1→printfgcc–cmain.cgcc–static–omyprocmain.o./mylib.a开始E、U、D为空,首先扫描main.o,把它加入E,同时把myfun1加入U,main加入D。接着扫描到mylib.a,将U中所有符号(本例中为myfunc1)与mylib.a中所有目标模块(myproc1.o和myproc2.o)依次匹配,发现在myproc1.o中定义了myfunc1,故myproc1.o加入E,myfunc1从U转移到D。在myproc1.o中发现还有未解析符号printf,将其加到U。不断在mylib.a的各模块上进行迭代以匹配U中的符号,直到U、D都不再变化。此时U中只有一个未解析符号printf,而D中有main和myfunc1。因为模块myproc2.o没有被加入E中,因而它被丢弃。E
将被合并以组成可执行文件的所有目标文件集合U当前所有未解析的引用符号的集合D当前所有定义符号的集合
接着,扫描默认的库文件libc.a,发现其目标模块printf.o定义了printf,于是printf也从U移到D,并将printf.o加入E,同时把它定义的所有符号加入D,而所有未解析符号加入U。处理完libc.a时,U一定是空的。
libc.a无需明显指出!链接器中符号解析的全过程
main.cvoidmyfunc1(viod);intmain(){myfunc1();return0;}
gcc–cmain.cgcc–static–omyprocmain.o./mylib.a问题:若命令为:gcc–static–omyproc./mylib.amain.o,结果怎样?main.o中的myfunc1不能被解析,故出现链接错误!链接顺序问题假设调用关系如下:func.o→libx.a和liby.a中的函数
libx.a→libz.a中的函数
libx.a和liby.a之间、liby.a和libz.a相互独立则以下几个命令行都是可行的:gcc-static–omyfuncfunc.olibx.aliby.alibz.agcc-static–omyfuncfunc.oliby.alibx.alibz.agcc-static–omyfuncfunc.olibx.alibz.aliby.a假设调用关系如下:func.o→libx.a和liby.a中的函数
libx.a→liby.a同时liby.a→libx.a则以下命令行可行:gcc-static–omyfuncfunc.olibx.aliby.alibx.a重定位符号解析完成后,可进行重定位工作,分两步对节和定义符号进行重定位将集合E的所有目标模块中相同的节合并成新节,并将运行时的虚拟地址赋给每个新节中所有的定义符号。例如,所有.text节合并作为可执行文件中的.text节,并为每个.text节确定在新.text节中的绝对地址,从而为其中定义的函数确定首地址(含有多个函数时),进而确定每条指令的地址。完成这一步后,每条指令和每个全局变量都可确定地址。对节中的引用符号进行重定位修改.text节和.data节中对每个符号的引用(地址)。需要用到在.rel_data和.rel_text节中保存的重定位信息。重定位信息汇编器遇到对位置未知的目标引用时,生成一个重定位条目数据引用的重定位条目在.rel_data节中指令中引用的重定位条目在.rel_text节中ELF中重定位条目格式如下:有两种最基本的重定位类型R_386_PC32:使用32位PC相对地址的引用,重定位指令R_386_32:使用32位绝对地址重定位数据typedefstruct{ intoffset;/*需重定位的引用的节偏移*/ intsymbol:24,
/*需重定位的引用所指向的符号*/t
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025-2030年中国酒杯(酒具)市场运营现状及投资前景规划研究报告
- 2025-2030年中国西乐器制造市场发展状况及前景趋势分析报告
- 岳西事业编招聘年考试真题及答案解析事业单位真题
- 长江大学文理学院《区域分析方法计量地理学》2023-2024学年第二学期期末试卷
- 2025甘肃省建筑安全员《A证》考试题库及答案
- 常州工程职业技术学院《化工环保与安全概论》2023-2024学年第二学期期末试卷
- 石家庄城市经济职业学院《第二语言教学法》2023-2024学年第二学期期末试卷
- 湖南安全技术职业学院《商业伦理与会计职业操守》2023-2024学年第二学期期末试卷
- 汕头大学《财政与金融》2023-2024学年第二学期期末试卷
- 浙江师范大学行知学院《公共部门绩效评估》2023-2024学年第二学期期末试卷
- 全隐框玻璃幕墙施工方案
- 十八项医疗核心制度详解培训课件
- 五年级上册信息技术教学计划华科版
- 机器人传感器PPT完整全套教学课件
- 初一语文下册:阅读理解知识点整理
- 营销部安全生产责任制
- CSM工法双轮铣水泥土搅拌墙专项施工方案
- 定点医疗机构接入验收申请表
- 小羊诗歌大全1479首(小羊喝水扩句)
- 2022-2023学年辽宁省鞍山市普通高中高一年级下册学期第一次月考数学(A卷)试题【含答案】
- 中国农村居民储蓄行为研究共3篇
评论
0/150
提交评论