Preprocessor Output
最近重新开始回顾 C 语言以及其编译后的文件格式 ELF。
暂时告别一步到位的命令 gcc main.c
,如果从 .c
文件的编译来说,主要分为预编译(preprocess)、编译(Compilation)、汇编(Assembly)、链接(Linking) 四个步骤。
但是,仅仅从第一个流程 预编译 而言,就已经遇到了一些麻烦。
program.i
# 1 "program.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "program.c"
# 1 "header.h" 1
char *test(void);
# 2 "program.c" 2
int main(void)
{
}
预编译后的问题出现了诸如 # 1 "program.c"
的 注释?
这里简单记录预处理输出文件的基本格式,方便今后回顾。
理解 Linux Kernel (2) - 多任务切换
概述
《只是为了好玩》一书中,林纳斯描述过他最早的试验性程序就是让 CPU 执行两个不同的任务(一个不断输出A,另一个输出B),同时不断地让 CPU 在两个任务间的切换。
结合《Linux 内核完全注释》一书,得到了多任务切换的示例程序。
本节所要描述的内容,正是结合一个框架式的汇编程序(多任务切换程序, 书中提供的内容比较老,无法适应目前的各种工具、环境),在现有环境中加以处理并成功运行。
关于运行环境的说明,欢迎参考 理解 Linux Kernel (0)
理解 Linux Kernel (1) - BIOS
前言
在概述,我想我已经介绍过我开始这一些列博文的原因。 我不能担保我所进行的所有试验性操作都是对的,但是,至少这些在我所描述的环境下成功的运行了起来,并帮助我触及我始终敬畏而又敬而远之的硬件&OS
在《Linux内核完全注释》一书第三章——内核编程语言和环境,描述了用 as86 汇编语言构建 boot 引导程序,并最终通过 Bochs 仿真器在模拟开机通电后,BIOS Load boot 程序,通过显示器输出 Loading System... 。
这些,将是这里所要描述的主要内容。
理解 Linux Kernel (0) - 概述
概述
之所以想要了解 Linux Kernel,与前一个月的 JVM ClassFile 的学习有这很大的关联。
说实话,刨除 JVM 的具体实现,Sun (或者现在该说是 Oracle) 确实将 Java 的底层逻辑设计得相当简单。
- 有限而统一的指令集(不超过 256 个,可以用 1 字节表示)
- 操作数栈+局部变量表共同实现的指令运算
- 高度封装的成员变量/方法的寻址方式
- ... (见识短浅,想不到了...以后再补充吧)
但是,与 JVM 模拟的虚拟机不同,实体机器有着更为复杂的结构。 最根本的,不同厂商的机器就带来了不同的 CPU 指令集,这就已经让人难以接受了。
其实最初,是想要继续去看看 Hotspot 虚拟机的。但是,混杂的代码(C++, Java, 平台相关各种实现) 带来了很大的阅读障碍。 最根本的,我甚至找不到一个学习的基本立足点,出发点(当然,也可能我根本就没仔细去看,哈哈)。
借着一次机会,我开始看《程序员的自我修养——链接、装载与库》一书。确实无论是 ELF 格式,静态链接与动态链接甚至 Linux 的内建函数 都给了我比较深刻的印象,向我展示了 C 更深入的一面。但是,macOS 给我带来了比较大的客观阻碍(即使用 Docker 容器得到了一个可用的 Linux 环境,但是否与直接建立在机器上的系统有所区别,此处还得打个问号)。 同时,书中的部分内容上下不统一,缺少前后文也是一个重大的问题。总之,这并不完全适合我这种初学者逐一进行书中所描述的全部实验。
最后,我决定展开对 Linux Kernel 的学习,试图通过对直接构架在硬件上的操作系统进行一番比较深入的学习。
期间,找过一些资料,也了解到 Linus Torvals 直接领导着的 Kernel 项目的官网;甚至,找到了各个版本的 kernel 源码(虽然确实地丢失了最早期 0.XX 的若干版本)。 不得不说,就目前来讲,我觉得《Linux内核完全注释》是最适合(打个问号,至少暂时是的)我学习的一书。
之后的若干博文,将都以《Linux内核完全注释》中的内容为基础,结合目前的实际需要,从而进行实操性的认知与学习。
Java Instrumentation
Start
从现有的前置知识来说,我们能够认识到两个事实:
- Java Class 通过 ClassLoader 进行加载。
通过
全限定名
进行区分。当需要加载新的类时,ClassLoader 通过双亲委派机制判断是否已经加载过这个类。 换句话说: Class 一经加载,就不会尝试重复加载 (至少按绝大多数人的认知来说,确实是的) - 有没有可能让被加载的 Class 与物理存储上的 .class 内容不同。 当然也是完全可以做到的。不管怎么说,CGlib 和 Java Proxy 也是一个耳熟能详的概念吧 (虽然可能不了解细节。在此,欢迎学习前置技能 CGlib Enhancer 主流程源码解析 和 Java Proxy 源码解析。不过不影响本文后续内容)
另一个方面,也许绝大多数人都听说过所谓的热部署
。但是究竟怎么才能做到 热部署
(话题开得有点大哈。Y_Y 本文不讲这个)
操作字节码一定是一个逃不开的话题,毕竟 Class 就是所谓的被加载到内存的字节码嘛。
如何操作字节码? ASM, CGlib, Java Proxy, Javassist ? 不过这些都要等到需要被操作的类被加载了才行啊,似乎有点晚...
Java 提供了一个可行的机制,用来在 ClassLoader 加载字节码之前完成对操作字节码的目的
Instrumentation
java.lang.instrument.Instrumentation
类为提供直接操作 Java 字节码的又一个途径(虽然 Java Doc 的说明是用来检测 Java 代码的)
相信我这个说明是没有问题的。毕竟完成对代码检测的途径是直接修改字节码。
下列有两种方法可以达到目的
- 当 JVM 以指示一个代理类的方式启动时,将传递给代理类的 premain 方法一个 Instrumentation 实例。
- 当 JVM 提供某种机制在 JVM 启动之后某一时刻启动代理时,将传递给代理代码的 agentmain 方法一个 Instrumentation 实例。
话不多说,下面将全部以实例来展现对这种 JVM 检测机制(虽然例子已经脱离了检测的目的)的使用
Java Proxy 源码解析
在 Java 整个生态里面, 通用的有两类动态代理的应用: Java Proxy 与 CGlib 代理。
从宽泛的区别来说,Java Proxy 只能对接口进行增强,而 CGlib 同时适用于类和接口的增强。 而且,业内普遍的认知是,CGlib 动态代理较之于 Java Proxy 在生成字节码的速度上也更为高效。
如何方便地获取 CGlib 生成类
配置参数
命令行使用
在 java 启动命令中添加参数配置项 -Dcglib.debugLocation=<Custom Path>
编码实现
在执行 CGlib 获取新生成类之前,调用 System.setProperty("cglib.debugLocation", <Custom Path>)
CGlib Enhancer 主流程源码解析
前言
此博文写作的目的:
- (Core) 通过了解 CGlib Enhancer 的整个调用链,了解其对于唯一依赖的 ASM 库的调用方式。
- 基于 Enhancer 对已有字节码进行增强的进一步理解与掌握。