3.4.1. 自我修改的程序码
在电脑时代的早期,memory是很珍贵的。人们不遗馀力地减少程序的大小,以为程序资料腾出更多的空间。一个经常使用的技巧是,随著时间改变程序自身。偶尔仍旧会找到这种自我修改的程序码(Self Modifying Code,SMC),如今多半是为了效能因素、或者用在安全漏洞上。
一般来说应该避免 SMC。虽然它通常都被正确地执行,但有著并非如此的边界案例(boundary case),而且没有正确完成的话,它会产生效能问题。显然地,被改变的程序码无法维持在保存被解码指令的追踪cache中。但即使程序码完全(或者有时候)不会被执行,因而不会使用到追踪cache,处理器也可能会有问题。若是接下来的指令在它已经进入管线的期间被改变,处理器就得丢掉大量的成果,然后从头开始。甚至有处理器的大多状态都必须被丢弃的情况。
最后,由于处理器假定 –– 为了简化起见,而且因为这在 99.9999999% 的情况下都成立 –– 程序码分页是不可修改的(immutable),所以 L1i 的实作不会采用 MESI 协议,而是一种简化的 SI 协议。这表示,若是侦测到修改,就必须做出许多的悲观假设。
强烈地建议尽可能避免 SMC。memory不再是如此稀有的资源。最好是撰写各自的函式,而非根据特定的需求修改一个函式。或许有天 SMC 的支援能够是可选的,而我们就能够以这种方式侦测出尝试修改程序码的漏洞程序码(exploit code)。若是真的必须使用 SMC,写入操作应该要绕过cache,以免因为 L1i 所需的 L1d 的资料造成问题。关于这些指令的更多讯息,见 6.1 节。
在 Linux 上,识别出包含 SMC 的程序通常非常容易。使用正规工具链(toolchain)建构的话,所有程序的程序码都是防写的(write-protected)。程序开发者必须在连结期(link time)施展强大的魔法,以产生程序分页能够被写入的可执行档。当这种情况发生时,现代的 Intel x86 与 x86-64 处理器具有专门的、计算自我修改的程序码使用次数的效能计数器。有了这些计数器的帮助,非常轻易就能够识别有著 SMC 的程序,即使程序由于宽松的许可而成功执行。