NJUOS操作系统上的程序

文章探讨了NJUOS操作系统上程序的执行机制,涉及到数字逻辑电路、宏展开、状态机的概念。通过示例展示了宏如何简化代码,以及递归和非递归汉诺塔问题的解决方案。此外,还讨论了程序在二进制层面的状态,包括内存、寄存器、动态链接和系统调用,并提到了ELF文件格式及链接器的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


title: NJUOS:操作系统上的程序
date: 2023-03-23 17:07:06
tags:

  • NJUOS

http://jyywiki.cn/OS/2022/slides/2.slides#/

操作系统上的程序

数字电路与状态机

数字逻辑电路:模拟器

“¬”、“∧”、“∨”非与或

X′=¬XY

Y′=¬X∧¬Y

#include <stdio.h>
#include <unistd.h>
#define REGS_FOREACH(_)  _(X) _(Y)
#define RUN_LOGIC        X1 = !X && Y; \    //用于多行宏
                         Y1 = !X && !Y;
#define DEFINE(X)        static int X, X##1; //两个##是连接符,即把两个宏变量拼接到一起
#define UPDATE(X)        X = X##1;
#define PRINT(X)         printf(#X " = %d; ", X); //#的作用就是把后面的参数当做一个字符串,也就是说等同于把后面的宏变量加上双引号
int main() {
  REGS_FOREACH(DEFINE)
  while (1) { // clock
    RUN_LOGIC
    REGS_FOREACH(PRINT)
    REGS_FOREACH(UPDATE)
    putchar('\n'); sleep(1);
  }
}   
可以用gcc -E 看下预处理结果
int main() {
  static int X, X1; static int Y, Y1;
  while (1) {
    X1 = !X && Y; Y1 = !X && !Y;
    printf("X" " = %d; ", X); printf("Y" " = %d; ", Y);
    X = X1; Y = Y1;
    putchar('\n'); sleep(1);
  }
}
grxer@grxer ~/D/s/N/p2> ./simulator 
X = 0; Y = 0; 
X = 0; Y = 1; 
X = 1; Y = 0; 
X = 0; Y = 0; 
X = 0; Y = 1; 
X = 1; Y = 0; 

嵌套宏的展开

  • 一般的宏嵌套展开规则是由内向外,先将内层宏展开,再把外层宏展开
  • 如果宏的参数直接带有#,则不会展开内层的嵌套宏
  • 如果宏的参数直接带有##,则会先将参数通过##拼接,然后再依次进行展开

这里采用了一个X-MACRO的高级宏的写法,个人认为就是写一个宏再利用宏嵌套去代替我们做一些重复性的工作

x-macro创建一段自我维护和相互依赖的代码。当程序的一部分发生变化导致另一部分发生变化时,就可以说代码是相互依赖的。

X宏的优势

  • X-Macros 通过创建单独的头文件以实现可维护,广泛用于操作系统开发
  • 有助于轻松维护复杂的编程
  • 它可以创建一段自我维护和相互依赖的代码

X宏的缺点

  • 代码变得不那么可读了
  • 代码很难理解
  • 通常仅用于内部编程,如 OS 编程。

tldr

Too Long ;Didn’t Read

帮助我们更快的从man手册里获取信息

什么是程序

源代码视角

程序就是状态机

  • 状态 = 堆 + 栈
  • 初始状态 = main 的第一条语句
  • 迁移 = 执行一条简单语句

递归的汉诺塔,经典分治算法,每次都把n-1当作一个整体,把n-1挪到辅助位,把最下面移到目标,再把辅助位上n-1移到目标

void hanoi(int n, char from, char to, char via) {
  if (n == 1) printf("%c -> %c\n", from, to);
  else {
    hanoi(n - 1, from, via, to);
    hanoi(1,     from, to,  via);
    hanoi(n - 1, via,  to,  from);
  }
  return;
}

非递归,个人感觉其实还是一个递归的思想,只是我们没有用c来帮助我们调用函数,而是在c的层面上做了一层抽象,把底层的栈帧给抽象出来,来模拟调用,让我们更形象的看到状态的转移,理解程序是个状态机

typedef struct {
  int pc, n;
  char from, to, via;
} Frame;

#define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; })
#define ret()     ({ top--; })
#define goto(loc) ({ f->pc = (loc) - 1; })

void hanoi(int n, char from, char to, char via) {
  Frame stk[64], *top = stk - 1;
  call(n, from, to, via);
  for (Frame *f; (f = top) >= stk; f->pc++) {
    switch (f->pc) {
      case 0: if (f->n == 1) { printf("%c -> %c\n", f->from, f->to); goto(4); } break;
      case 1: call(f->n - 1, f->from, f->via, f->to);   break;
      case 2: call(       1, f->from, f->to,  f->via);  break;
      case 3: call(f->n - 1, f->via,  f->to,  f->from); break;
      case 4: ret();                                    break;
      default: assert(0);
    }
  }
}

二进制视角

还是状态机

  • 状态 = 内存 M + 寄存器 R
  • 初始状态 = 动态链接器ld去做动态链接=ld-linux-x86-64.so加载libc
  • 迁移 = 执行一条指令

所有的指令都只能计算

想要操作一些硬件资源必须经过操作系统,系统调用syscall

程序 = 计算 + syscall

#include <sys/syscall.h>

.globl _start
_start:
  movq $SYS_write, %rax   // write(
  movq $1,         %rdi   //   fd=1,
  movq $st,        %rsi   //   buf=st,
  movq $(ed - st), %rdx   //   count=ed-st
  syscall                 // );

  movq $SYS_exit,  %rax   // exit(
  movq $1,         %rdi   //   status=1
  syscall                 // );//这里直接是个内核态的syscall

st:
  .ascii "\033[01;31mHello, OS World\033[0m\n"
ed:
;gcc -c minimal.S && ld minimal.o 

我们之前做过一些x86——32和64的系统调用分析https://grxer.gitee.io/2023/03/05/sd-police2023-pwn/

  1. cpp–>*.i(ascii中间文件) 预处理器 gcc -E 预处理 预处理器先将#include #define(宏)等预处理指令进行替换和展开–hello.i
  2. cc1–>*.s(ascii汇编语言文件) 编译器 gcc -S 编译 编译器产生两个源文件的汇编代码p1.s p2.s–hello.s
  3. as–>*.o(可重定位目标文件) 汇编器 gcc -c 汇编 汇编器将汇编代码转化为可重定位目标代码文件 p1.o p2.o 但是没有填入全局值的地址 hell.o
  4. ld–>*.out(可执行目标文件) 链接器 ld *.o 链接 链接器将目标代码文件与库函数(如printf)合并 生成可执行文件p链接器的任务之一就是为函数调用找到匹配的函数的可执行代码的位置

main() 的开始/结束并不是整个程序的开始/结束

#include <stdio.h>

__attribute__((constructor)) void hello() {
  printf("Hello, World\n");
}

// See also: atexit(3)
__attribute__((destructor)) void goodbye() {
  printf("Goodbye, Cruel OS World!\n");
}

int main() {
}

在这里插入图片描述

这个我们在https://grxer.gitee.io/2023/03/10/Chunk-Extend-and-Overlapping/ 做过fini_array hook分析

其实就是和这个有关

.init_array.fini_array中存放了指向初始化代码和终止代码的函数指针。

.init_array会在main()函数调用前执行,这样可以通过修该地址的指针来将控制流指向病毒或者寄生代码,因为比main执行还早,大部分恶意软件都是hook这个

.fini_array 函数指针在 main() 函数执行完之后才被触发

.init_array array[0]->array[1]

.fini_array array[1]->array[0]

我们把程序丢到ida来看一下

在这里插入图片描述

在这里插入图片描述

谁规定是 ld-linux-x86-64.so,而不是 rtfm.so

这个我们之前也做过分析https://grxer.gitee.io/2023/03/19/23-2-21-elf/

在ELF的Program Header Table 的Interpreter Path segment里规定了解释器地址,我们在有一个属于自己合理的解释器的前提下,可以完全hack掉这个程序执行流

starce

https://grxer.gitee.io/2023/03/05/sd-police2023-pwn/ 里拿来分析过ls系统调用

总结

RTFM----Read The Fucking Manual

STFW----Search The Fucking Web

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值