Docker搭建i386实验环境,gcc多态实现

简介: 使用Docker搭建了i386的实验环境,并且使用多种工具验证了gcc实现多态的机制。

Docker设置

首先选择ubuntu 20.04镜像,按照docker官网指引,安装docker。

之后编写 Dockerfile ,如下

FROM scratch
ADD ubuntu-bionic-oci-i386-root.tar.gz /
WORKDIR /
COPY rootfs /
RUN apt update
RUN apt install -y openssh-server psmisc net-tools

由于host系统已经是64位ubuntu了,所以直接 FROM scratch ,之后将从![ubuntu oci image](https://partner-images.canonical.com/oci/)获取得到的32位ubuntu rootfs拷贝到容器中的根目录。之后将项目目录中 rootfs 拷贝到容器的根目录,覆盖sources.list、覆盖ssh-key、root账号的authorized_key。

之后编写 docker-compose.yml 如下,将ssh的22端口映射到host机器的5022,设置启动运行脚本,防止栈地址随机化。

version: "3"services:  i386:    container_name: i386
    image: i386
    build:      context: DockerBuild/i386
      dockerfile: Dockerfile
    command: /bin/bash /docker-entrypoint.d/run.sh
    ports:      - "5022:22"    volumes:      - ./DockerBuild/i386:/docker-entrypoint.d
# GDB    security_opt:      - seccomp:unconfined
    cap_add:      - SYS_PTRACE

测试代码

主要测试C++中虚函数表的实现方法,基本的测试代码如下:

classPlainPerson{
public:
constchar*get_job() {
return"none";
    }
};
classPerson {
public:
virtualvoidplaceholder() {};
virtualconstchar*get_job() {
return"none";
    }
};
classStudent: publicPerson {
public:
virtualconstchar*get_job() {
return"student";
    }
};
voidprint_job(Person*p){
printf("job for 0x%X is '%s'\n", p, p->get_job());
}
intmain() {
printf("sizeof(void)         = %d\n", sizeof(void));
printf("sizeof(PlainPersono) = %d\n", sizeof(PlainPerson));
printf("sizeof(Persono)      = %d\n", sizeof(Person));
Person*p0=newPerson();
Person*p1=newStudent();
print_job(p0);
print_job(p1);
return0;
}

实验结果

使用 g++ -fno-pic -no-pie main.cpp 编译代码,使用 objdump -Mintel -d a.out 查看反汇编。

08048546 <_Z9print_jobP6Person>:
 8048546:       55                      push   ebp
 8048547:       89 e5                   mov    ebp,esp
 8048549:       83 ec 08                sub    esp,0x8
 804854c:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 804854f:       8b 00                   mov    eax,DWORD PTR [eax]
 8048551:       83 c0 04                add    eax,0x4
 8048554:       8b 00                   mov    eax,DWORD PTR [eax]
 8048556:       83 ec 0c                sub    esp,0xc
 8048559:       ff 75 08                push   DWORD PTR [ebp+0x8]
 804855c:       ff d0                   call   eax
 804855e:       83 c4 10                add    esp,0x10
 8048561:       83 ec 04                sub    esp,0x4
 8048564:       50                      push   eax
 8048565:       ff 75 08                push   DWORD PTR [ebp+0x8]
 8048568:       68 0d 87 04 08          push   0x804870d
 804856d:       e8 9e fe ff ff          call   8048410 <printf@plt>
 8048572:       83 c4 10                add    esp,0x10
 8048575:       90                      nop
 8048576:       c9                      leave
 8048577:       c3                      ret

函数里面有两个 call ,应该分别对应 get_jobprintf 。首先第一个 call 的地址来自寄存器 eax,除去调整栈大小和传参操作,在 call 之前只剩下4如下指令:

mov    eax,DWORD PTR [ebp+0x8]   // 获取第一个参数,即 Person* p;
mov    eax,DWORD PTR [eax]       // f* f_arr = *(f**)(p);
add    eax,0x4                   
mov    eax,DWORD PTR [eax]       // f func = f_arr[1];

说明编译器将实现多态的虚函数表指针(即 f**),放到了对象的头4字节中。

由于 virtual void placeholder() 的影响,获取 get_name 的实际位置需要访问虚函数表中的第二项(第一项留给了来自父类的placeholder函数)。

使用gdb验证我们的猜想,如下所示:

(gdb) b *0x804855c
Breakpoint 1 at 0x804855c
(gdb) c
The program is not being run.
(gdb) run
Starting program: /root/test/run 
sizeof(void)         = 1
sizeof(PlainPersono) = 1
sizeof(Persono)      = 4
Breakpoint 1, 0x0804855c in print_job(Person*) ()
(gdb) i r eax
eax            0x804863a  134514234
(gdb) x $eax
0x804863a <_ZN6Person7get_jobEv>: 0xb8e58955

根据反编译结果打断点,之后使用 x 命令查看 eax 寄存器对应内存区域的内容,显示是一个函数。查看反汇编可以确认此函数就是实际调用的虚函数。

总结

GCC将虚函数表的指针放到对象的前4个字节,在调用时候,从虚函数表中获取真实的函数指针。这种设计只要保证父类和子类的虚函数表对应位置保持相同语义即可。

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
10月前
|
应用服务中间件 Linux nginx
在虚拟机Docker环境下部署Nginx的步骤。
以上就是在Docker环境下部署Nginx的步骤。需要注意,Docker和Nginix都有很多高级用法和细节需要掌握,以上只是一个基础入门级别的教程。如果你想要更深入地学习和使用它们,请参考官方文档或者其他专业书籍。
443 5
|
12月前
|
缓存 Ubuntu Docker
Ubuntu环境下删除Docker镜像与容器、配置静态IP地址教程。
如果遇见问题或者想回滚改动, 可以重启系统.
667 16
|
消息中间件 监控 Docker
Docker环境下快速部署RabbitMQ教程。
就这样,你成功地用魔法召唤出了RabbitMQ,还把它和你的应用程序连接了起来。现在,消息会像小溪流水一样,在你的系统中自由流淌。别忘了,兔子们不喜欢孤独,他们需要你细心的关怀,不时地监控它们,确保他们的世界运转得井井有条。
773 18
|
消息中间件 监控 Docker
Docker环境下快速部署RabbitMQ教程。
至此,这次神秘而简明的部署之旅告一段落。祝你在利用RabbitMQ打造消息队列时,一切顺风顺水!
807 8
|
弹性计算 关系型数据库 Nacos
低配阿里云 ECS 如何 docker 环境部署 NACOS : 单机版模式
NACOS 单机版 Docker 安装指南。使用指定端口和 custom.env 配置文件启动 Nacos 服务,适用于 2.X 版本,包含 gRPC 支持及 MySQL 数据源配置。 -e MODE=standalone \
965 5
|
Ubuntu 机器人 开发者
Docker环境下的ROS Noetic:Ubuntu 20.04 系统下的解决方案
这就是在Docker环境下安装ROS Noetic在Ubuntu 20.04系统的一种简单方法,希望能对你有所帮助。
1274 16
|
存储 缓存 Serverless
【Azure Container App】如何在Consumption类型的容器应用环境中缓存Docker镜像
在 Azure 容器应用的 Consumption 模式下,容器每次启动均需重新拉取镜像,导致冷启动延迟。本文分析该机制,并提出优化方案:使用 ACR 区域复制加速镜像拉取、优化镜像体积、设置最小副本数减少冷启动频率,或切换至 Dedicated 模式实现镜像缓存,以提升容器启动效率和应用响应速度。
581 0
|
关系型数据库 MySQL Linux
在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾
以上就是在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾的步骤。这个过程就像是一场接力赛,数据从MySQL数据库中接力棒一样传递到备份文件,再从备份文件传递到其他服务器,最后再传递回MySQL数据库。这样,即使在灾难发生时,我们也可以快速恢复数据,保证业务的正常运行。
627 28
|
算法 Shell 定位技术
在Docker环境下搭建openvslam/orb_slam3的步骤和问题总结
总的来说,搭建openvslam或orb_slam3的过程需要一些耐心和技术知识,但只要你遵循上述步骤,并且在遇到问题时进行适当的调试,你应该能够成功搭建并运行openvslam或orb_slam3。
548 11