HNU-计算机系统-Challenge

Challenge

计科210X wolf 202108010XXX

本题是从属于第七次讨论课的个人题,听说做了有加分?我来试试。
下面是相关报告。

在这里插入图片描述
结束榜单:不错不错,打了第8名。
在这里插入图片描述

题目:

C 语言的初学者第一个编写的 C 代码一般是如下所示的“

#include <stdio.h>
int main() {
 printf("Hello, World!");
 return 0;
}

但这区区四行代码,在 Ubuntu 中使用 gcc 形成的可执行文件大小可能有 7KB 之多,请尝试分析 ta 到底增加了什么内容导致可执行文件的大小产生这样的膨胀?基于前述分析,如果代码是这样的:

int main(void) { return 2023; }

不修改这一行 C 代码源程序,你可以直接修改二进制可执行文件,获取功能没有任何变化的更小(字节数量)可执行文件吗?分享你的方法(^-^)

可参考http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html

尝试1:

注意:使用

./a.out ; echo $? ; wc -c a.out

可以查看返回值并查看大小

若返回值为231(2023%256的结果)则答案正确。

①Strip Symbols(剔除符号表)
gcc -s 7.c -o try.out
./a.out ; echo $? ; wc -c a.out

输出正确,此时5512字节

②Optimization(编译器优化)

考虑O3优化

gcc -s -O3 7.c -o try.out
./a.out ; echo $? ; wc -c a.out

输出正确,此时5512字节,没有改变

③Remove Standard Library(移除标准库)

使用gcc -nostdlib可以关闭链接标准库的过程,这可以省去一大笔开销。

gcc -s -O3 -nostdlib 7.c -o try.out 
./a.out ; echo $? ; wc -c a.out

分别输出如下

/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000080480e0

段错误 (核心已转储)
139
688 try.out

虽然大小小到了688,但是出现了段错误,这是因为程序本来的入口应该是_start,这是标准库中提供的。但是如果强制关闭了标准库会导致找不到程序入口,出现段错误。此时我们需要补写一个_start函数。

//_start.s
.section .data
    stack:
        .space 4   # 分配一个 4 字节的栈空间

.section .text
.globl _start

_start:
    # 设置堆栈指针
    movl $stack+4, %esp

    # 调用 main 函数
    call main

    # 将返回值存储在 %ebx 中
    movl %eax, %ebx

    # 调用退出系统调用
    movl $1, %eax
    int $0x80

分别使用gcc和as将_start.s和7.c编译成.o文件,再使用ld将其链接生成可执行文件test.out。最后测试该可执行文件

gcc -c -s -O3 -nostdlib 7.c -o 7.o
as _start.s -o _start.o
ld _start.o 7.o -o test.out
#最后进行测试
./test.out ; echo $? ; wc -c test.out

输出正确,此时895字节,还能凹分!

④不从源文件拷贝符号信息和relocation信息

使用readelf test.out查看elf文件。发现符号信息和relocation信息仍然存在仍然存在,可以优化。

在之前的基础上再加上objcopy test.out -S try.out ,我们不需要符号信息和relocation信息。

现在的操作为

gcc -c -s -O3 -nostdlib 7.c -o 7.o
as _start.s -o _start.o
ld _start.o 7.o -o test.o
objcopy test.o -S try.out
#最后进行测试
./test.out ; echo $? ; wc -c try.out

输出正确,此时560字节,似乎已经到了一个探索的瓶颈了。

⑤Custom Linker Script(手写链接脚本)

这个主要是因为注意到ELF中会有很多对齐的现象,这是链接器做的工作,这里可以凹分。

先来学习链接脚本怎么写,一个简单的链接脚本如下。

ENTRY ( symbol )
SECTIONS
    . = 0x10000;
    .text :  *(.text) #假设代码应该在地址' 0x10000 '加载
    . = 0x8000000;
    .data :  *(.data) #数据应该从地址' 0x8000000 '加载
    .bss :  *(.bss) 
#' * '是一个通配符,可以匹配任何文件名

我们仿照地写一个

ENTRY(_start) #写不写其实无所谓了
SECTIONS
{
  . = 0x8048000 + SIZEOF_HEADERS;

  tiny : { *(.text) *(.data) *(.rodata*) }

  /DISCARD/ : { *(*) }
}

运行,发现报错如下

`main' referenced in section `.text' of _start.o: defined in discarded section `.text.startup' of 7.o
#`在_start.o的“.text”节中引用的main:在7.o的已丢弃的“.text.startup”节中定义

显然我们对discard节的处理出了问题,尝试许久后发现,真的不知道该怎么写了。

不妨看一看ld为我们自带的是怎么写的。

#使用ld --verbose查看linker script逻辑
ENTRY(_start)
SEARCH_DIR("/usr/i686-linux-gnu/lib32"); SEARCH_DIR("=/usr/local/lib32"); SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
  .rel.dyn        :
    {
      *(.rel.init)
      *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
      *(.rel.fini)
      *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
      *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
      *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
      *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
      *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
      *(.rel.ctors)
      *(.rel.dtors)
      *(.rel.got)
      *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
      *(.rel.ifunc)
    }
  .rel.plt        :
    {
      *(.rel.plt)
      PROVIDE_HIDDEN (__rel_iplt_start = .);
      *(.rel.iplt)
      PROVIDE_HIDDEN (__rel_iplt_end = .);
    }
  .init           :
  {
    KEEP (*(.init))
  } =0x90909090
  .plt            : { *(.plt) *(.iplt) }
  .text           :
  {
    *(.text.unlikely .text.*_unlikely)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  } =0x90909090
  .fini           :
  {
    KEEP (*(.fini))
  } =0x90909090
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .eh_frame_hdr : { *(.eh_frame_hdr) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table
  .gcc_except_table.*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges
  .exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) }
  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata	  : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
  .tbss		  : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array     :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array))
    KEEP (*(EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array     :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array))
    KEEP (*(EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { KEEP (*(.jcr)) }
  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { *(.dynamic) }
  .got            : { *(.got) *(.igot) }
  . = DATA_SEGMENT_RELRO_END (12, .);
  .got.plt        : { *(.got.plt)  *(.igot.plt) }
  .data           :
  {
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  __bss_start = .;
  .bss            :
  {
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we don't
      pad the .data section.  */
   . = ALIGN(. != 0 ? 32 / 8 : 1);
  }
  . = ALIGN(32 / 8);
  . = ALIGN(32 / 8);
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { *(.stab) }
  .stabstr       0 : { *(.stabstr) }
  .stab.excl     0 : { *(.stab.excl) }
  .stab.exclstr  0 : { *(.stab.exclstr) }
  .stab.index    0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment       0 : { *(.comment) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}

于是,我们尝试自己写一遍

//link.lds
ENTRY(_start)
SECTIONS
{
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;
  .init           :
  {
    KEEP (*(.init))
  } =0x90909090
  .text           :
  {
    *(.text.startup .text.startup.*)
  } =0x90909090
}

使用如下代码进行我们自定义的链接

 ld -T link.lds _start.o 7.o -o test.o ;

总结,操作如下:

//文件夹下有link.lds _start.s 7.c三个文件
gcc -c -s -O3 -nostdlib 7.c -o 7.o
as _start.s -o _start.o
ld -T link.lds _start.o 7.o -o test.o
objcopy test.o -S try.o
./try.o ; echo $?
wc -c try.o

输出正确,此时528字节。已经到了本方法的一个尽头了。

尝试2:

自己写一段汇编代码文件,并编译。

#try2.asm
.section .data
    stack:
        .space 4   # 分配一个 4 字节的栈空间

.section .text
.globl _start

_start:
    # 设置堆栈指针
    movl $stack+4, %esp

    # 调用 main 函数
    movl $0x7e7, %eax

    # 将返回值存储在 %ebx 中
    movl %eax, %ebx

    # 调用退出系统调用
    movl $1, %eax
    int $0x80

操作码为

as try2.s -o try2.o
ld -T link.lds try2.o -o test.o
objcopy test.o -S try.out
./try.out ; echo $?
wc -c try.o

输出正确,结果为292字节。

尝试3:

参考网站http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html(即提供的网站)

(……怎么说呢,通过操作实现让最后的ELF为如下就可以完成,操作的方法参见网站)

(放在这里仅仅是提供一个参考而已,没去实现,这是想都不敢想的实现路径)

  ; tiny.asm
  BITS 32
                org     0x00010000
                db      0x7F, "ELF" 
                dd      1
                dd      0
                dd      $$
                dw      2
                dw      3
                dd      _start
                dd      _start
                dd      4
  _start:
                mov     bl, 2023
                xor     eax, eax
                inc     eax
                int     0x80
                db      0
                dw      0x34
                dw      0x20
                dw      1
                dw      0
                dw      0
                dw      0
  filesize      equ     $ - $$

操作如下

nasm -f bin -o a.out tiny.asm
chmod +x a.out
./a.out ; echo $?
 wc -c a.out

输出正确,结果为52字节。(网站上还有更小的)

总结

  • 尝试1是最为正宗的做法,从编译器、链接器、符号表等角度去进行优化。
  • 尝试2只是一个想法,实现了相同的效果。
  • 尝试3复制粘贴并改动了网站上的代码,能实现相同的效果,可以去试试看。
  • 花费了5月15一整晚(22:00到3:30)与5月16一下午的时间
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值