C语言余数为0输出intact,小话c语言

汇编和c只有一步之近----小话c语言(19)

作者:陈曦

日期:2012-6-810:50:13

环境:[Ubuntu11.04  Intel-based x64 gcc4.5.2  CodeBlocks10.05  AT&T汇编  Intel汇编]

转载请注明出处

Q:举个例子吧。

A:下面的代码的目标是计算1+2的值,最后放到变量temp中,并输出:

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)              printf(#str" is %s\n",(str));

static void assemble_func()

{

int temp;

__asm__("mov $1, %eax");

__asm__("mov $2, %ebx");

__asm__("add %ebx, %eax"); // 1 + 2

__asm__("mov %%eax, %0":"=r"(temp));    // mov the value of register eax to the var"temp"

PRINT_D(temp)               // print temp

}

int main()

{

assemble_func();

return 0;

}

运行结果:

[plain] view plaincopy

temp is 3

Q: assemble_func函数的汇编代码形式是什么?

A:

[cpp] view plaincopy

0x08048404 :   push   ebp

0x08048405 :   mov    ebp,esp

0x08048407 :   push   ebx

0x08048408 :   sub    esp,0x24

=> 0x0804840b :    mov   eax,0x1

0x08048410 :  mov    ebx,0x2

0x08048415 :  add    eax,ebx

0x08048417 :  mov    ebx,eax

0x08048419 :  mov    DWORD PTR [ebp-0xc],ebx

0x0804841c :  mov    eax,0x8048510

0x08048421 :  mov    edx,DWORD PTR [ebp-0xc]

0x08048424 :  mov    DWORD PTR [esp+0x4],edx

0x08048428 :  mov    DWORD PTR [esp],eax

0x0804842b :  call   0x8048340

0x08048430 :  add    esp,0x24

0x08048433 :  pop    ebx

0x08048434 :  pop    ebp

0x08048435 :  ret

上面的汇编是在调试运行到assemble_func函数的开始时,使用disassemble命令得到的数据。注意第五行左侧的箭头符号是调试状态显示正在运行的行数。

Q:上面的汇编是内嵌到c代码中的,单独完全的汇编代码,如何实现hello world的功能?

A:从本质上说,只用汇编的形式需要对于底层更了解,c代码从编译的角度来说和汇编没什么区别,只是写的格式以及调用的东西看起来不一致罢了。

如下,是实现标准控制台输出功能的代码:

[cpp] view plaincopy

.section .rodata

str:

.ascii "Hello,world.\n"

.section .text

.globl _main

_main:

movl $4,    %eax    # the number of system call

movl $1,    %ebx    # file descriptor, 1 means stdout

movl $str,  %ecx    # string address

movl $13,   %edx    # string length

int  $0x80

保存为hello.s.

Q:如何编译它,使用gcc吗?

A:当然可以,不过这个文件显然不需要预处理了,它已经是汇编格式了,不需要单纯狭义的编译过程了,只需要从汇编过程开始了。

它可以直接生成目标文件hello.o

Q:接下来做什么?可以直接执行它吗?

A:试试。

此时,给hello.o添加可执行权限再执行:

Q:这是为什么?

A:继续观察hello.o文件的属性。

可以看出,它还不是可执行文件。其实很简单,hello.o只是目标文件,并没有链接成可执行文件。

Q:这又是为什么?没有找到入口符号_start, ld默认的入口符号是_start?

A:是的。在代码中使用的是_main, 所以应该让链接器明白,入口符号是_main.

Q:现在应该可以运行了吧。运行一下:

Hello,world是输出了,为什么后面会出现段错误呢?

A:我们首先看看上面的运行返回了什么。

返回值为139,它代表什么?

Q:从系统的errno.h头文件以及相关文件中查找,得到所有系统错误码:

/usr/include/asm-generic/errno-base.h文件:

[cpp] view plaincopy

#ifndef _ASM_GENERIC_ERRNO_BASE_H

#define _ASM_GENERIC_ERRNO_BASE_H

#define EPERM        1 /* Operation not permitted */

#define ENOENT       2 /* No such file or directory */

#define ESRCH        3 /* No such process */

#define EINTR        4 /* Interrupted system call */

#define EIO      5 /* I/O error */

#define ENXIO        6 /* No such device or address */

#define E2BIG        7 /* Argument list too long */

#define ENOEXEC      8 /* Exec format error */

#define EBADF        9 /* Bad file number */

#define ECHILD      10 /* No child processes */

#define EAGAIN      11 /* Try again */

#define ENOMEM      12 /* Out of memory */

#define EACCES      13 /* Permission denied */

#define EFAULT      14 /* Bad address */

#define ENOTBLK     15 /* Block device required */

#define EBUSY       16 /* Device or resource busy */

#define EEXIST      17 /* File exists */

#define EXDEV       18 /* Cross-device link */

#define ENODEV      19 /* No such device */

#define ENOTDIR     20 /* Not a directory */

#define EISDIR      21 /* Is a directory */

#define EINVAL      22 /* Invalid argument */

#define ENFILE      23 /* File table overflow */

#define EMFILE      24 /* Too many open files */

#define ENOTTY      25  /*Not a typewriter */

#define ETXTBSY     26 /* Text file busy */

#define EFBIG       27 /* File too large */

#define ENOSPC      28 /* No space left on device */

#define ESPIPE      29 /* Illegal seek */

#define EROFS       30 /* Read-only file system */

#define EMLINK      31 /* Too many links */

#define EPIPE       32 /* Broken pipe */

#define EDOM        33 /* Math argument out of domain of func */

#define ERANGE      34 /* Math result not representable */

#endif

/usr/include/asm-generic/errno.h文件:

[cpp] view plaincopy

#ifndef _ASM_GENERIC_ERRNO_H

#define _ASM_GENERIC_ERRNO_H

#include

#define EDEADLK     35 /* Resource deadlock would occur */

#define ENAMETOOLONG    36  /*File name too long */

#define ENOLCK      37 /* No record locks available */

#define ENOSYS      38 /* Function not implemented */

#define ENOTEMPTY   39  /*Directory not empty */

#define ELOOP       40 /* Too many symbolic links encountered */

#define EWOULDBLOCK EAGAIN  /* Operation would block */

#define ENOMSG      42 /* No message of desired type */

#define EIDRM       43 /* Identifier removed */

#define ECHRNG      44 /* Channel number out of range */

#define EL2NSYNC    45  /* Level 2 not synchronized */

#define EL3HLT      46 /* Level 3 halted */

#define EL3RST      47 /* Level 3 reset */

#define ELNRNG      48 /* Link number out of range */

#define EUNATCH     49 /* Protocol driver not attached */

#define ENOCSI      50 /* No CSI structure available */

#define EL2HLT      51 /* Level 2 halted */

#define EBADE       52 /* Invalid exchange */

#define EBADR       53 /* Invalid request descriptor */

#define EXFULL      54 /* Exchange full */

#define ENOANO      55 /* No anode */

#define EBADRQC     56 /* Invalid request code */

#define EBADSLT     57 /* Invalid slot */

#define EDEADLOCK   EDEADLK

#define EBFONT      59 /* Bad font file format */

#define ENOSTR      60 /* Device not a stream */

#define ENODATA     61 /* No data available */

#define ETIME       62 /* Timer expired */

#define ENOSR       63 /* Out of streams resources */

#define ENONET      64 /* Machine is not on the network */

#define ENOPKG      65 /* Package not installed */

#define EREMOTE     66 /* Object is remote */

#define ENOLINK     67 /* Link has been severed */

#define EADV        68 /* Advertise error */

#define ESRMNT      69 /* Srmount error */

#define ECOMM       70 /* Communication error on send */

#define EPROTO      71 /* Protocol error */

#define EMULTIHOP   72  /*Multihop attempted */

#define EDOTDOT     73 /* RFS specific error */

#define EBADMSG     74 /* Not a data message */

#define EOVERFLOW   75  /*Value too large for defined data type */

#define ENOTUNIQ    76 /* Name not unique on network */

#define EBADFD      77 /* File descriptor in bad state */

#define EREMCHG     78 /* Remote address changed */

#define ELIBACC     79 /* Can not access a needed shared library */

#define ELIBBAD     80 /* Accessing a corrupted shared library */

#define ELIBSCN     81 /* .lib section in a.out corrupted */

#define ELIBMAX     82 /* Attempting to link in too many shared libraries */

#define ELIBEXEC    83 /* Cannot exec a shared library directly */

#define EILSEQ      84 /* Illegal byte sequence */

#define ERESTART    85 /* Interrupted system call should be restarted */

#define ESTRPIPE    86 /* Streams pipe error */

#define EUSERS      87 /* Too many users */

#define ENOTSOCK    88 /* Socket operation on non-socket */

#define EDESTADDRREQ    89 /* Destination address required */

#define EMSGSIZE    90 /* Message too long */

#define EPROTOTYPE  91  /*Protocol wrong type for socket */

#define ENOPROTOOPT 92  /* Protocol not available */

#define EPROTONOSUPPORT 93  /* Protocol not supported */

#define ESOCKTNOSUPPORT 94  /* Socket type not supported */

#define EOPNOTSUPP  95  /*Operation not supported on transport endpoint */

#define EPFNOSUPPORT    96 /* Protocol family not supported */

#define EAFNOSUPPORT    97 /* Address family not supported by protocol */

#define EADDRINUSE  98  /*Address already in use */

#define EADDRNOTAVAIL   99  /*Cannot assign requested address */

#define ENETDOWN    100 /* Network is down */

#define ENETUNREACH 101 /* Network isunreachable */

#define ENETRESET   102 /* Network dropped connection because ofreset */

#define ECONNABORTED    103 /* Software caused connection abort*/

#define ECONNRESET  104 /* Connection reset by peer */

#define ENOBUFS     105 /* No buffer space available */

#define EISCONN     106 /* Transport endpoint is alreadyconnected */

#define ENOTCONN    107 /* Transport endpoint is not connected*/

#define ESHUTDOWN   108 /* Cannot send after transport endpointshutdown */

#define ETOOMANYREFS    109 /* Too many references: cannot splice*/

#define ETIMEDOUT   110 /* Connection timed out */

#define ECONNREFUSED    111 /* Connection refused */

#define EHOSTDOWN   112 /* Host is down */

#define EHOSTUNREACH    113 /* No route to host */

#define EALREADY    114 /* Operation already in progress*/

#define EINPROGRESS 115 /* Operation now inprogress */

#define ESTALE      116 /* Stale NFS file handle */

#define EUCLEAN     117 /* Structure needs cleaning */

#define ENOTNAM     118 /* Not a XENIX named type file */

#define ENAVAIL     119 /* No XENIX semaphores available*/

#define EISNAM      120 /* Is a named type file */

#define EREMOTEIO   121 /* Remote I/O error */

#define EDQUOT      122 /* Quota exceeded */

#define ENOMEDIUM   123 /* No medium found */

#define EMEDIUMTYPE 124 /* Wrong mediumtype */

#define ECANCELED   125 /* Operation Canceled */

#define ENOKEY      126 /* Required key not available */

#define EKEYEXPIRED 127 /* Key has expired*/

#define EKEYREVOKED 128 /* Key has beenrevoked */

#define EKEYREJECTED    129 /* Key was rejected by service */

/* for robust mutexes */

#define EOWNERDEAD  130 /* Owner died */

#define ENOTRECOVERABLE 131 /* State notrecoverable */

#define ERFKILL     132 /* Operation not possible due toRF-kill */

#endif

就是没有找到139.

A:看来,系统已经发生一些诡异的情况,错误码已经不正确了。为了确定139错误码确实不存在,我们在/usr/include目录下递归搜索139这个字符。

[plain] view plaincopy

grep -R '139' *

结果比较长,这里不列出来来。依然没有能找到系统对应的139错误定义。

那么,我们来看看系统日志吧,到底哪里可能有问题。

Q:使用如下命令得到了错误信息:

最后的地方确实看到hello应用程序运行错误的系统日志。应该是指针访问出错。原因是否是汇编代码大最后没有恰当地设置堆栈寄存器等寄存器的值呢?

A:在这里,很有可能。为了更容易看出问题可能在哪里,写一个类似功能的c代码,得到它的汇编代码,和上面的汇编代码进行比较。

Q:写了如下的hello_1.c代码如下:

[cpp] view plaincopy

#include

int main()

{

printf("Hello,world!\n");

return 0;

}

查看它的汇编代码:

[cpp] view plaincopy

.file   "hello_1.c"

.section    .rodata

.LC0:

.string "Hello,world!"

.text

.globl main

.type   main, @function

main:

pushl   %ebp

movl    %esp, %ebp

andl    $-16, %esp

subl    $16, %esp

movl    $.LC0, (%esp)

call    puts

movl    $0, %eax

leave

ret

.size   main, .-main

.ident  "GCC: (Ubuntu/Linaro4.5.2-8ubuntu4) 4.5.2"

.section   .note.GNU-stack,"",@progbits

果然,和hello.s代码确实有不一样。这里,开始执行时对ebp, esp进行了处理,最后使用了leave和ret命令。就是它们引起的吗?

A:不过在实际中,不管是加入pushl  %ebp之类代码,还是加入leave, ret指令,最终执行依然是段错误。这个地方笔者一直没明白,如果有谁知道的,希望能不吝赐教。不过,可以调用exit系统调用实现结束应用程序,这样就不会出现段错误。如下:

[cpp] view plaincopy

.section .rodata

str:

.ascii "Hello,world.\n"

.section .text

.globl _main

_main:

movl $4,    %eax    # the number of system call

movl $1,    %ebx    # file descriptor, 1 means stdout

movl $str,  %ecx    # string address

movl $13,   %edx    # string length

int  $0x80

movl $1,    %eax

movl $0,    %ebx

int  $0x80

运行结果:

Q:进行0x80软中断进行系统调用,参数在哪里保存,就在上面写的寄存器里面吗?

A:是的。linux下,功能号和返回值在eax中保存,参数一般在5个以下,就按照ebx, ecx, edx, esi, edi来传递,如果参数过多,就会使用堆栈。可以看到上面两次系统调用,均是在使用ebx,ecx, edx这些寄存器。

Q: 4号系统调用是什么?在哪里能知道?

A:可以在/usr/include/asm/unistd_32.h或者/usr/include/asm/unistd_64.h中看到平台所有系统调用,下面为unistd_32.h文件中开始一部分:

[cpp] view plaincopy

#define __NR_restart_syscall      0

#define __NR_exit         1

#define __NR_fork         2

#define __NR_read         3

#define __NR_write        4

#define __NR_open         5

#define __NR_close        6

#define __NR_waitpid          7

#define __NR_creat        8

#define __NR_link         9

#define __NR_unlink      10

#define __NR_execve      11

#define __NR_chdir       12

#define __NR_time        13

#define __NR_mknod       14

#define __NR_chmod       15

#define __NR_lchown      16

#define __NR_break       17

可以看到,1号系统调用为exit, 4号为write, 正是上面代码使用的。

Q:汇编如何调用c库函数?

A:使用call指令,不过调用前要传好参数。如下代码,调用c库printf函数:

[cpp] view plaincopy

.section .rodata

str:

.ascii "Hello,world.\n"

.section .text

.globl main

main:

pushl  $str

call   printf

pushl  $0

call   exit

保存为printf.s, 编译:

运行:

Q:可以使用as, ld来汇编以及链接吗?

A:可以的。不过需要注意,因为它使用c库,需要指定链接c库: -lc;

Q:  乘法运算mul后面只跟着一个数,另一个数存哪里?

A:另一个数存储在al, ax或者eax寄存器中,这取决于使用的是mulb, mulw还是mull指令。结果将按照高位到地位的顺序保存在dx和ax中。

同理,除法运算div后面也只跟一个除数,被除数保存在ax, dx:ax或者edx:eax中。除数的最大长度只能是被除数的一半。商和余数将根据被除数占用大小来确定:

如果被除数在ax中,商在al, 余数在ah; 如果被除数在eax中,商在ax, 余数在dx; 如果被除数在edx:eax中,商在eax, 余数在edx.

如下是测试代码:

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)              printf(#str" is %s\n",(str));

static void assemble_func()

{

int result_high, result_low;

short result, remainder;

//mul

__asm__("mov $10, %eax");

__asm__("mov $10, %ebx");

__asm__("mull %ebx");

__asm__("mov %%edx, %0":"=r"(result_high));

__asm__("mov %%eax, %0":"=r"(result_low));

PRINT_D(result_high)

PRINT_D(result_low)

// div

__asm__("mov $0,  %dx");

__asm__("mov $100, %ax");  // the divident is dx:ax

__asm__("mov $9, %bx");

__asm__("div %bx");        // the divisor is bx

__asm__("movw %%ax, %0":"=r"(result));

__asm__("movw %%dx, %0":"=r"(remainder));

PRINT_D(result)

PRINT_D(remainder)

}

int main()

{

assemble_func();

return 0;

}

输出结果:

[plain] view plaincopy

result_high is 0

result_low is 100

result is 11

remainder is 1

Q:  对于数据比较指令cmp,它是如何配合jmp相关的指令?

A:  cmp指令将进行两个数据的差计算,如果得到的是0,jz成立; 如果不是0, jnz成立。如下例子:

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)          printf(#str" is %s\n",(str));

#define PRINT(str)              printf(#str"\n");

static void assemble_func()

{

__asm__("mov $10, %eax");

__asm__("cmp $10, %eax ");

__asm__("jz  end");

PRINT("below jz")

__asm__("end:");

PRINT("the end")

}

int main()

{

assemble_func();

return 0;

}

显然,jz会成立,输出如下:

[plain] view plaincopy

"the end"

Q:对于某些时候,加法可能导致溢出,如何判断出来?

A: CPU内部有一个寄存器,它内部会保存溢出标志位OF, 可以通过jo或者jno判断。

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)          printf(#str" is %s\n",(str));

#define PRINT(str)              printf(#str"\n");

static void assemble_func()

{

__asm__("movw   $0x7FFF,  %ax");

__asm__("movw   $0x7FFF,  %bx");

__asm__("addw   %bx,      %ax");

__asm__("jo    overflow_set");

__asm__("movl   $1,       %eax");

__asm__("movl   $0,       %ebx");

__asm__("int   $0x80");

__asm__("overflow_set:");

PRINT("overflow flag is set...")

}

int main()

{

assemble_func();

return 0;

}

运行结果:

[plain] view plaincopy

"overflow flag is set..."

Q:对于溢出,到底应该判断?

A:以加法举例,如果两个相同符号的数相加得到的结果符号相反,那么一定溢出了。

Q: OF和CF标志位有什么区别?

A: CF代表进位标志。进位不一定是溢出,比如有符号整形最小值加1,虽然进位,但是没溢出。因为计算机补码的理论允许进位,但是结果却正确。

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)          printf(#str" is %s\n",(str));

#define PRINT(str)              printf(#str"\n");

static void assemble_func()

{

__asm__("movw   $0xFFFF,  %ax");

__asm__("movw   $0x1,  %bx");

__asm__("addw   %bx,      %ax");

__asm__("je    carry_set");

__asm__("movl   $1,       %eax");

__asm__("movl   $0,       %ebx");

__asm__("int   $0x80");

__asm__("carry_set:");

PRINT("carry flag is set...")

}

int main()

{

assemble_func();

return 0;

}

运行结果:

[plain] view plaincopy

"carry flag is set..."

当然,我们可以用jo来测试上面的加法是否溢出。

[cpp] view plaincopy

#include

#include

#define PRINT_D(longValue)      printf(#longValue" is %ld\n",((long)longValue));

#define PRINT_STR(str)          printf(#str" is %s\n",(str));

#define PRINT(str)              printf(#str"\n");

static void assemble_func()

{

__asm__("movw   $0xFFFF,  %ax");

__asm__("movw   $0x1,  %bx");

__asm__("addw   %bx,      %ax");

__asm__("jo    overflow_set");

__asm__("movl   $1,       %eax");

__asm__("movl   $0,       %ebx");

__asm__("int   $0x80");

__asm__("overflow_set:");

PRINT("overflow flag is set...")

}

int main()

{

assemble_func();

return 0;

}

执行结果:

它什么也没输出,这就意味着OF没有被置位。

作者:陈曦

日期:2012-6-810:50:13

环境:[Ubuntu11.04  Intel-based x64 gcc4.5.2  CodeBlocks10.05  AT&T汇编 Intel汇编]

转载请注明出处:#32�=3>�2查看某个函数的汇编形式,怎么办?

A: 可以使用-p参数来指定查看的函数。如下:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image020.gif

上面查看了hello中的main函数的汇编形式(注意,虽然代码中是main函数,但是在可执行文件的符号表中,可能会被改变,这依赖编译器).

Q: 有的可执行文件支持不止一种硬件架构的代码,如何查看包含哪些?

A: 可以使用file命令查看。如上,查看上面的hello文件的信息:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image022.gif

可以看到它是64位运行于x64平台上的可执行文件。

Q: 如何编译得到一个可执行文件,它是通用的,可以包含几种架构代码的?

A: 使用gcc的-arch参数功能来实现。依然使用上面的hello.c代码:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif

再运行file命令:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif

可以看到,文件包含了两种架构体系代码。

Q: 看到很多逆向工具,可以修改特定的目标文件实现特定的目的,是否可以修改hello文件来得到希望的执行效果?

A: 是的。我并不善于修改汇编代码,我们可以修改数据段来查看效果。

修改上面的hello.c代码如下:

1.     #include

2.

3.     int g_i = 0xAA;

4.     const int g_j = 0xBB;

5.     char *str = "hello";

6.

7.     int main()

8.     {

9.         int i = 2;

10.      printf("%s %d\n", str, i);

11.      return 0;

12.  }

编译成hello.并查看执行效果:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif

下面就使用vi来修改可执行文件的hello字符串为iello,最后查看执行效果。使用vi打开hello文件,定位到hello字符串的位置:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif

修改hello的h为i:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.gif

保存,然后执行hello:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.gif

可以看到,执行结果已经变了。

Q: 有时,需要查看一个目标文件中各个段的大小,如何查看?

A: 可以使用size命令。

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.gif

Q: 有时,需要查看目标文件中可打印的字符串有哪些,如何查看?

A: 可以使用strings命令。

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.gif

Q: 很多编译好的程序,里面包含了符号表或者一些调试信息,如何去除它们?

A: 可以使用strip命令。

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.gif

使用strip命令后:

file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.gif

可以看到,符号信息确实少了。

Q: 上面提到的nm命令,它是做什么的?

A: 它是可以查看目标文件中包含的符号信息的。

xichen

2012-5-2512:50:52

[本帖最后由 tiankai001 于 2012-12-15 07:59 编辑]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值