80386机器代码,105字节
代码的十六进制转储:
60 8b fa 81 ec 00 41 00 00 33 c0 8b f4 33 d2 42
89 14 06 42 33 ed 8b d8 03 2c 1e 2a fa 73 f9 83
c6 04 89 2c 06 42 3b d1 76 ea fe c4 3a e1 76 db
33 d2 0f c7 f0 f7 f5 86 e9 85 d2 74 1b 33 c0 8d
34 0c 39 14 86 77 03 40 eb f8 2b 54 86 fc 40 89
07 83 c7 04 2a e8 77 e1 42 89 17 83 c7 04 fe cd
7f f7 4a b6 41 03 e2 61 c3
作为C函数: void random_partition(int n, int result[]);
。它以提供的缓冲区中的数字列表形式返回结果;它不会以任何方式标记列表的结尾,但是用户可以通过累积数字来发现结尾-当总和等于时,列表结束n
。
使用方法(在Visual Studio中):
#include <stdio.h>
__declspec(naked) void __fastcall random_partiton(int n, int result[])
{
#define a(byte) __asm _emit 0x ## byte
a(60) a(8b) a(fa) a(81) a(ec) a(00) a(41) a(00) a(00) a(33) a(c0) a(8b) a(f4) a(33) a(d2) a(42)
a(89) a(14) a(06) a(42) a(33) a(ed) a(8b) a(d8) a(03) a(2c) a(1e) a(2a) a(fa) a(73) a(f9) a(83)
a(c6) a(04) a(89) a(2c) a(06) a(42) a(3b) a(d1) a(76) a(ea) a(fe) a(c4) a(3a) a(e1) a(76) a(db)
a(33) a(d2) a(0f) a(c7) a(f0) a(f7) a(f5) a(86) a(e9) a(85) a(d2) a(74) a(1b) a(33) a(c0) a(8d)
a(34) a(0c) a(39) a(14) a(86) a(77) a(03) a(40) a(eb) a(f8) a(2b) a(54) a(86) a(fc) a(40) a(89)
a(07) a(83) a(c7) a(04) a(2a) a(e8) a(77) a(e1) a(42) a(89) a(17) a(83) a(c7) a(04) a(fe) a(cd)
a(7f) a(f7) a(4a) a(b6) a(41) a(03) a(e2) a(61) a(c3)
}
void make_stack() // see explanations about stack below
{
volatile int temp[65 * 64];
temp[0] = 999;
}
int main()
{
int result[100], j = 0, n = 64, counter = n;
make_stack(); // see explanations about stack below
random_partiton(n, result);
while (counter > 0)
{
printf("%d ", result[j]);
counter -= result[j];
++j;
}
putchar('\n');
}
输出示例(n = 64):
21 7 4 4 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1
这需要很多解释...
当然,我使用了其他所有人也都使用过的算法;对于复杂性没有任何选择。因此,我不必过多解释该算法。无论如何:
我用不大于f(n, m)
的n
部分来表示元素的划分数量m
。我将它们存储在一个二维数组(在C中声明为f[65][64]
)中,第一个索引为n
,第二个为m-1
。我决定支持n=65
太麻烦了,于是放弃了……
这是计算此表的C代码:
#define MAX_M 64
int f[(MAX_M + 1) * MAX_M];
int* f2;
int c; // accumulates the numbers needed to calculate f(n, m)
int m;
int k; // f(k, m), for various values of k, are accumulated
int n1;
for (n1 = 0; n1 <= n; ++n1)
{
f2 = f;
f2[n1 * MAX_M] = 1;
for (m = 2; m <= n; ++m)
{
c = 0;
k = n1;
while (k >= 0)
{
c += f2[k * MAX_M];
k -= m;
}
++f2;
f2[n1 * MAX_M] = c;
}
}
该代码具有一些混淆的样式,因此可以轻松地将其转换为汇编语言。它最多计算元素f(n, n)
,即n
元素的分区数。该代码完成后,临时变量将c
包含所需的数字,该数字可用于选择随机分区:
int index = rand() % c;
稍后,index
使用生成的表将其转换为所需的格式(数字列表)。
do {
if (index == 0)
break;
m = 0;
f2 = &f[n * MAX_M];
while (f2[m] <= index)
{
++m;
}
index -= f2[m-1];
++m;
*result++ = m;
n -= m;
} while (n > 0);
do {
*result++ = 1;
--n;
} while (n > 0);
该代码也经过了优化,可转换为汇编语言。有一个小“ bug”:如果分区1
末尾不包含任何数字,则最后一个循环遇到n = 0
,并输出不需要的1
元素。但这没有什么坏处,因为打印代码会跟踪该数字的总和,并且不会打印此无关的数字。
当转换为内联汇编时,此代码如下所示:
__declspec(naked) void _fastcall random_partition_asm(int n, int result[])
{
_asm {
pushad;
// ecx = n
// edx = m
// bh = k; ebx = k * MAX_M * sizeof(int)
// ah = n1; eax = n1 * MAX_M * sizeof(int)
// esp = f
// ebp = c
// esi = f2
// edi = result
mov edi, edx;
sub esp, (MAX_M + 1) * MAX_M * 4; // allocate space for table
xor eax, eax;
row_loop:
mov esi, esp;
xor edx, edx;
inc edx;
mov dword ptr [esi + eax], edx;
inc edx;
col_loop:
xor ebp, ebp;
mov ebx, eax;
sum_loop:
add ebp, [esi + ebx];
sub bh, dl;
jae sum_loop;
add esi, 4;
mov [esi + eax], ebp;
inc edx;
cmp edx, ecx;
jbe col_loop;
inc ah;
cmp ah, cl;
jbe row_loop;
// Done calculating the table
// ch = n; ecx = n * MAX_M * sizeof(int)
// eax = m
// ebx =
// edx = index
// esp = f
// esi = f2
// ebp = c
// edi = result
xor edx, edx;
rdrand eax; // generate a random number
div ebp; // generate a random index in the needed range
xchg ch, cl; // multiply by 256
n_loop:
test edx, edx;
jz out_trailing;
xor eax, eax;
lea esi, [esp + ecx];
m_loop:
cmp [esi + eax * 4], edx;
ja m_loop_done;
inc eax;
jmp m_loop;
m_loop_done:
sub edx, [esi + eax * 4 - 4];
inc eax;
mov [edi], eax;
add edi, 4;
sub ch, al;
ja n_loop;
out_trailing:
inc edx;
out_trailing_loop:
mov dword ptr [edi], edx;
add edi, 4;
dec ch;
jg out_trailing_loop;
dec edx;
mov dh, (MAX_M + 1) * MAX_M * 4 / 256;
add esp, edx;
popad;
ret;
}
}
一些有趣的事情要注意:
- 生成随机数仅需3个字节的机器代码(
rdrand
指令)
- 碰巧的是,表的大小是64,所以一行的大小是256个字节。我用它在“高字节”寄存器(如)中保存行索引
ah
,这使我可以自动乘以256。为了利用这一点,我牺牲了对n = 65
。我希望我可以为这种罪过感到宽恕...
通过从堆栈指针寄存器中减去0x4100来执行堆栈上的分配空间esp
。这是一个6字节的指令!将这个数字加回去时,我设法用5个字节来做:
dec edx; // here edx = 1 from earlier calculations
mov dh, (MAX_M + 1) * MAX_M * 4 / 256; // now edx = 0x4100
add esp, edx; // this deallocates space on stack
在MS Visual Studio中调试此功能时,我发现将数据写入其在堆栈上分配的空间时会崩溃!深入研究后,我发现了某种堆栈溢出保护:OS似乎只为堆栈分配了非常有限的虚拟地址范围;如果某个函数访问的地址太远,则OS会认为它超出了限制并杀死了该程序。但是,如果函数具有许多局部变量,则OS会做一些额外的“魔术”操作。因此,我必须调用一个在堆栈上分配了一个大数组的空函数。此函数返回后,将分配并使用额外的堆栈VM页面。
void make_stack()
{
volatile int temp[65 * 64];
temp[0] = 999; // have to "use" the array to prevent optimizing it out
}