18.3 结构体tm

18.3.1 linux

在Linux下,我们看看time.h中的tm结构体是什么样子的:

  1. #include <stdio.h>
  2. #include <time.h>
  3. void main()
  4. {
  5. struct tm t;
  6. time_t unix_time;
  7. unix_time=time(NULL);
  8. localtime_r (&unix_time, &t);
  9. printf ("Year: %d", t.tm_year+1900);
  10. printf ("Month: %d", t.tm_mon);
  11. printf ("Day: %d", t.tm_mday);
  12. printf ("Hour: %d", t.tm_hour);
  13. printf ("Minutes: %d", t.tm_min);
  14. printf ("Seconds: %d", t.tm_sec);
  15. };

在GCC 4.4.1下编译得到:

清单18.6:GCC 4.4.1

  1. main proc near
  2. push ebp
  3. mov ebp, esp
  4. and esp, 0FFFFFFF0h
  5. sub esp, 40h
  6. mov dword ptr [esp], 0 ; first argument for time()
  7. call time
  8. mov [esp+3Ch], eax
  9. lea eax, [esp+3Ch] ; take pointer to what time() returned
  10. lea edx, [esp+10h] ; at ESP+10h struct tm will begin
  11. mov [esp+4], edx ; pass pointer to the structure begin
  12. mov [esp], eax ; pass pointer to result of time()
  13. call localtime_r
  14. mov eax, [esp+24h] ; tm_year
  15. lea edx, [eax+76Ch] ; edx=eax+1900
  16. mov eax, offset format ; "Year: %d"
  17. mov [esp+4], edx
  18. mov [esp], eax
  19. call printf
  20. mov edx, [esp+20h] ; tm_mon
  21. mov eax, offset aMonthD ; "Month: %d"
  22. mov [esp+4], edx
  23. mov [esp], eax
  24. call printf
  25. mov edx, [esp+1Ch] ; tm_mday
  26. mov eax, offset aDayD ; "Day: %d"
  27. mov [esp+4], edx
  28. mov [esp], eax
  29. call printf
  30. mov edx, [esp+18h] ; tm_hour
  31. mov eax, offset aHourD ; "Hour: %d"
  32. mov [esp+4], edx
  33. mov [esp], eax
  34. call printf
  35. mov edx, [esp+14h] ; tm_min
  36. mov eax, offset aMinutesD ; "Minutes: %d"
  37. mov [esp+4], edx
  38. mov [esp], eax
  39. call printf
  40. mov edx, [esp+10h]
  41. mov eax, offset aSecondsD ; "Seconds: %d"
  42. mov [esp+4], edx ; tm_sec
  43. mov [esp], eax
  44. call printf
  45. leave
  46. retn
  47. main endp

可是,IDA并没有为本地栈上变量建立本地变量名。但是因为我们已经学了汇编了,我们也不需要在这么简单的例子里面如此依赖它。

请也注意一下lea edx, [eax+76ch],这个指令把eax的值加上0x76c,但是并不修改任何标记位。请也参考LEA的相关章节(B.6.2节)

为了表现出结构体只是一个个的变量连续排列的东西,让我们重新测试一下这个例子,我们看看time.h: 清单18.7 time.h

  1. struct tm
  2. {
  3. int tm_sec;
  4. int tm_min;
  5. int tm_hour;
  6. int tm_mday;
  7. int tm_mon;
  8. int tm_year;
  9. int tm_wday;
  10. int tm_yday;
  11. int tm_isdst;
  12. };
  13. #include <stdio.h>
  14. #include <time.h>
  15. void main()
  16. {
  17. int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday, tm_isdst;
  18. time_t unix_time;
  19. unix_time=time(NULL);
  20. localtime_r (&unix_time, &tm_sec);
  21. printf ("Year: %d", tm_year+1900);
  22. printf ("Month: %d", tm_mon);
  23. printf ("Day: %d", tm_mday);
  24. printf ("Hour: %d", tm_hour);
  25. printf ("Minutes: %d", tm_min);
  26. printf ("Seconds: %d", tm_sec);
  27. };

注:指向tm_sec的指针会传递给localtime_r,或者说第一个“结构体”元素。 编译器会这么警告我们

清单18.8 GCC4.7.3

  1. GCC_tm2.c: In function main’:
  2. GCC_tm2.c:11:5: warning: passing argument 2 of localtime_r from incompatible pointer type [
  3. enabled by default]
  4. In file included from GCC_tm2.c:2:0:
  5. /usr/include/time.h:59:12: note: expected struct tm *’ but argument is of type int *’

但是至少,它会生成这段代码:

清单18.9 GCC 4.7.3

  1. main proc near
  2. var_30 = dword ptr -30h
  3. var_2C = dword ptr -2Ch
  4. unix_time = dword ptr -1Ch
  5. tm_sec = dword ptr -18h
  6. tm_min = dword ptr -14h
  7. tm_hour = dword ptr -10h
  8. tm_mday = dword ptr -0Ch
  9. tm_mon = dword ptr -8
  10. tm_year = dword ptr -4
  11. push ebp
  12. mov ebp, esp
  13. and esp, 0FFFFFFF0h
  14. sub esp, 30h
  15. call __main
  16. mov [esp+30h+var_30], 0 ; arg 0
  17. mov [esp+30h+unix_time], eax
  18. lea eax, [esp+30h+tm_sec]
  19. mov [esp+30h+var_2C], eax
  20. lea eax, [esp+30h+unix_time]
  21. mov [esp+30h+var_30], eax
  22. call localtime_r
  23. mov eax, [esp+30h+tm_year]
  24. add eax, 1900
  25. mov [esp+30h+var_2C], eax
  26. mov [esp+30h+var_30], offset aYearD ; "Year: %d"
  27. call printf
  28. mov eax, [esp+30h+tm_mon]
  29. mov [esp+30h+var_2C], eax
  30. mov [esp+30h+var_30], offset aMonthD ; "Month: %d"
  31. call printf
  32. mov eax, [esp+30h+tm_mday]
  33. mov [esp+30h+var_2C], eax
  34. mov [esp+30h+var_30], offset aDayD ; "Day: %d"
  35. call printf
  36. mov eax, [esp+30h+tm_hour]
  37. mov [esp+30h+var_2C], eax
  38. mov [esp+30h+var_30], offset aHourD ; "Hour: %d"
  39. call printf
  40. mov eax, [esp+30h+tm_min]
  41. mov [esp+30h+var_2C], eax
  42. mov [esp+30h+var_30], offset aMinutesD ; "Minutes: %d"
  43. call printf
  44. mov eax, [esp+30h+tm_sec]
  45. mov [esp+30h+var_2C], eax
  46. mov [esp+30h+var_30], offset aSecondsD ; "Seconds: %d"
  47. call printf
  48. leave
  49. retn
  50. main endp

这个代码和我们之前看到的一样,依然无法分辨出源代码是用了结构体还是只是数组而已。

当然这样也是可以运行的,但是实际操作中还是不建议用这种晦涩的方法。因为通常,编译器会在栈上按照声明顺序分配变量空间,但是并不能保证每次都是这样。

还有,其他编译器可能会警告tm_year,tm_mon, tm_mday, tm_hour, tm_min变量而不是tm_sec使用时未初始化。事实上,计算机并不知道调用localtime_r()的时候他们会被自动填充上。

我选择了这个例子来解释是因为他们都是int类型的,而SYSTEMTIME的所有成员是16位的WORD,如果把它们作为本地变量来声明的话,他们会按照32位的边界值来对齐,因此什么都用不了了(因为由于数据对齐,此时GetSystemTime()会把它们错误的填充起来)。请继续读下一节的内容:“结构体的成员封装”。

所以,结构体只是把一组变量封装到一个位置上,数据是一个接一个的。我可以说结构体是一个语法糖,因为它只是用来让编译器把一组变量保存在一个地方。但是,我不是编程方面的专家,所以更有可能的是,我可能会误读这个术语。还有,在早期(1972年以前)的时候,C是不支持结构体的。

18.3.2 ARM+优化Keil+thumb模式

同样的例子: 清单18.10: 优化Keil+thumb模式

  1. var_38 = -0x38
  2. var_34 = -0x34
  3. var_30 = -0x30
  4. var_2C = -0x2C
  5. var_28 = -0x28
  6. var_24 = -0x24
  7. timer = -0xC
  8. PUSH {LR}
  9. MOVS R0, #0 ; timer
  10. SUB SP, SP, #0x34
  11. BL time
  12. STR R0, [SP,#0x38+timer]
  13. MOV R1, SP ; tp
  14. ADD R0, SP, #0x38+timer ; timer
  15. BL localtime_r
  16. LDR R1, =0x76C
  17. LDR R0, [SP,#0x38+var_24]
  18. ADDS R1, R0, R1
  19. ADR R0, aYearD ; "Year: %d"
  20. BL __2printf
  21. LDR R1, [SP,#0x38+var_28]
  22. ADR R0, aMonthD ; "Month: %d"
  23. BL __2printf
  24. LDR R1, [SP,#0x38+var_2C]
  25. ADR R0, aDayD ; "Day: %d"
  26. BL __2printf
  27. LDR R1, [SP,#0x38+var_30]
  28. ADR R0, aHourD ; "Hour: %d"
  29. BL __2printf
  30. LDR R1, [SP,#0x38+var_34]
  31. ADR R0, aMinutesD ; "Minutes: %d"
  32. BL __2printf
  33. LDR R1, [SP,#0x38+var_38]
  34. ADR R0, aSecondsD ; "Seconds: %d"
  35. BL __2printf
  36. ADD SP, SP, #0x34
  37. POP {PC}

18.3.3 ARM+优化Xcode(LLVM)+thumb-2模式

IDA“碰巧知道”tm结构体(因为IDA“知道”例如localtime_r()这些库函数的参数类型),所以他把这里的结构变量的名字也显示出来了。

  1. var_38 = -0x38
  2. var_34 = -0x34
  3. PUSH {R7,LR}
  4. MOV R7, SP
  5. SUB SP, SP, #0x30
  6. MOVS R0, #0 ; time_t *
  7. BLX _time
  8. ADD R1, SP, #0x38+var_34 ; struct tm *
  9. STR R0, [SP,#0x38+var_38]
  10. MOV R0, SP ; time_t *
  11. BLX _localtime_r
  12. LDR R1, [SP,#0x38+var_34.tm_year]
  13. MOV R0, 0xF44 ; "Year: %d"
  14. ADD R0, PC ; char *
  15. ADDW R1, R1, #0x76C
  16. BLX _printf
  17. LDR R1, [SP,#0x38+var_34.tm_mon]
  18. MOV R0, 0xF3A ; "Month: %d"
  19. ADD R0, PC ; char *
  20. BLX _printf
  21. LDR R1, [SP,#0x38+var_34.tm_mday]
  22. MOV R0, 0xF35 ; "Day: %d"
  23. ADD R0, PC ; char *
  24. BLX _printf
  25. LDR R1, [SP,#0x38+var_34.tm_hour]
  26. MOV R0, 0xF2E ; "Hour: %d"
  27. ADD R0, PC ; char *
  28. BLX _printf
  29. LDR R1, [SP,#0x38+var_34.tm_min]
  30. MOV R0, 0xF28 ; "Minutes: %d"
  31. ADD R0, PC ; char *
  32. BLX _printf
  33. LDR R1, [SP,#0x38+var_34]
  34. MOV R0, 0xF25 ; "Seconds: %d"
  35. ADD R0, PC ; char *
  36. BLX _printf
  37. ADD SP, SP, #0x30
  38. POP {R7,PC}
  39. ...
  40. 00000000 tm struc ; (sizeof=0x2C, standard type)
  41. 00000000 tm_sec DCD ?
  42. 00000004 tm_min DCD ?
  43. 00000008 tm_hour DCD ?
  44. 0000000C tm_mday DCD ?
  45. 00000010 tm_mon DCD ?
  46. 00000014 tm_year DCD ?
  47. 00000018 tm_wday DCD ?
  48. 0000001C tm_yday DCD ?
  49. 00000020 tm_isdst DCD ?
  50. 00000024 tm_gmtoff DCD ?
  51. 00000028 tm_zone DCD ? ; offset
  52. 0000002C tm ends

清单18.11: ARM+优化Xcode(LLVM)+thumb-2模式