当前位置: 首页 > wzjs >正文

猪八戒做网站排名推荐就业的培训机构

猪八戒做网站排名,推荐就业的培训机构,小米手机商城,网站做的好的公司目录 概要 一、汇编分析 1.1、go tool compile 1.2、dlv debug 二、栈布局与函数栈帧 概要 函数栈帧是指函数在被调用时,为该函数在栈上分配的一块内存区域(一般是连续的),主要用于保存函数的上下文信息,包括参数&…

目录

概要

一、汇编分析

1.1、go tool compile

1.2、dlv debug

 二、栈布局与函数栈帧


概要

函数栈帧是指函数在被调用时,为该函数在栈上分配的一块内存区域(一般是连续的),主要用于保存函数的上下文信息,包括参数,返回值,局部变量,寄存器值(ebp/rbp)等信息

调试的服务器信息:Centos Linux 7 ,CPU AMD x86_64,Go version 1.24

在go源码runtime/stack.go文件中可以看到go给出的在x86 CPU下栈布局:

// (x86)
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
// |  return address  |
// +------------------+
// |  caller's BP (*) | (*) if framepointer_enabled && varp > sp
// +------------------+ <- frame->varp
// |     locals       |
// +------------------+
// |  args to callee  |
// +------------------+ <- frame->sp

我们结合一下案例,结合汇编进行逐步分析:

 1 package main23 func main() {4     x,y:=int64(1),int64(2)5     x=add(x,y)6 }78 func add(a, b int64) int64 {9      c:=a+b10     q:=int64(5)11     p:=sub(c, q)12     return p13 }1415 func sub(x, y int64) int64{16     return x -y17 }

前置知识:

x86寄存器

x86汇编指令

go汇编
 

一、汇编分析

我们通过go tool compile和dlv两个工具结合来看。

1.1、go tool compile

[root@test gofunc]# go tool compile -S -N -l main.go
main.main STEXT size=66 args=0x0 locals=0x28 funcid=0x0 align=0x00x0000 00000 (/home/gofunc/main.go:3) TEXT    main.main(SB), ABIInternal, $40-00x0000 00000 (/home/gofunc/main.go:3) CMPQ    SP, 16(R14)0x0004 00004 (/home/gofunc/main.go:3) PCDATA  $0, $-2 //GC相关0x0004 00004 (/home/gofunc/main.go:3) JLS     580x0006 00006 (/home/gofunc/main.go:3) PCDATA  $0, $-1 //GC相关0x0006 00006 (/home/gofunc/main.go:3) PUSHQ   BP0x0007 00007 (/home/gofunc/main.go:3) MOVQ    SP, BP0x000a 00010 (/home/gofunc/main.go:3) SUBQ    $32, SP0x000e 00014 (/home/gofunc/main.go:3) FUNCDATA        $0, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB) //GC相关0x000e 00014 (/home/gofunc/main.go:3) FUNCDATA        $1, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB) //GC相关0x000e 00014 (/home/gofunc/main.go:4) MOVQ    $1, main.x+24(SP)0x0017 00023 (/home/gofunc/main.go:4) MOVQ    $2, main.y+16(SP)0x0020 00032 (/home/gofunc/main.go:5) MOVL    $1, AX0x0025 00037 (/home/gofunc/main.go:5) MOVL    $2, BX0x002a 00042 (/home/gofunc/main.go:5) PCDATA  $1, $00x002a 00042 (/home/gofunc/main.go:5) CALL    main.add(SB)0x002f 00047 (/home/gofunc/main.go:5) MOVQ    AX, main.x+24(SP)0x0034 00052 (/home/gofunc/main.go:6) ADDQ    $32, SP0x0038 00056 (/home/gofunc/main.go:6) POPQ    BP0x0039 00057 (/home/gofunc/main.go:6) RET0x003a 00058 (/home/gofunc/main.go:6) NOP0x003a 00058 (/home/gofunc/main.go:3) PCDATA  $1, $-10x003a 00058 (/home/gofunc/main.go:3) PCDATA  $0, $-20x003a 00058 (/home/gofunc/main.go:3) CALL    runtime.morestack_noctxt(SB)0x003f 00063 (/home/gofunc/main.go:3) PCDATA  $0, $-10x003f 00063 (/home/gofunc/main.go:3) NOP0x0040 00064 (/home/gofunc/main.go:3) JMP     00x0000 49 3b 66 10 76 34 55 48 89 e5 48 83 ec 20 48 c7  I;f.v4UH..H.. H.0x0010 44 24 18 01 00 00 00 48 c7 44 24 10 02 00 00 00  D$.....H.D$.....0x0020 b8 01 00 00 00 bb 02 00 00 00 e8 00 00 00 00 48  ...............H0x0030 89 44 24 18 48 83 c4 20 5d c3 e8 00 00 00 00 90  .D$.H.. ].......0x0040 eb be                                            ..rel 43+4 t=R_CALL main.add+0rel 59+4 t=R_CALL runtime.morestack_noctxt+0
main.add STEXT size=103 args=0x10 locals=0x38 funcid=0x0 align=0x00x0000 00000 (/home/gofunc/main.go:8) TEXT    main.add(SB), ABIInternal, $56-160x0000 00000 (/home/gofunc/main.go:8) CMPQ    SP, 16(R14)0x0004 00004 (/home/gofunc/main.go:8) PCDATA  $0, $-20x0004 00004 (/home/gofunc/main.go:8) JLS     760x0006 00006 (/home/gofunc/main.go:8) PCDATA  $0, $-10x0006 00006 (/home/gofunc/main.go:8) PUSHQ   BP0x0007 00007 (/home/gofunc/main.go:8) MOVQ    SP, BP0x000a 00010 (/home/gofunc/main.go:8) SUBQ    $48, SP0x000e 00014 (/home/gofunc/main.go:8) FUNCDATA        $0, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB)0x000e 00014 (/home/gofunc/main.go:8) FUNCDATA        $1, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB)0x000e 00014 (/home/gofunc/main.go:8) FUNCDATA        $5, main.add.arginfo1(SB)0x000e 00014 (/home/gofunc/main.go:8) MOVQ    AX, main.a+64(SP)0x0013 00019 (/home/gofunc/main.go:8) MOVQ    BX, main.b+72(SP)0x0018 00024 (/home/gofunc/main.go:8) MOVQ    $0, main.~r0+16(SP)0x0021 00033 (/home/gofunc/main.go:9) ADDQ    BX, AX0x0024 00036 (/home/gofunc/main.go:9) MOVQ    AX, main.c+40(SP)0x0029 00041 (/home/gofunc/main.go:10)        MOVQ    $5, main.q+24(SP)0x0032 00050 (/home/gofunc/main.go:11)        MOVL    $5, BX0x0037 00055 (/home/gofunc/main.go:11)        PCDATA  $1, $00x0037 00055 (/home/gofunc/main.go:11)        CALL    main.sub(SB)0x003c 00060 (/home/gofunc/main.go:11)        MOVQ    AX, main.p+32(SP)0x0041 00065 (/home/gofunc/main.go:12)        MOVQ    AX, main.~r0+16(SP)0x0046 00070 (/home/gofunc/main.go:12)        ADDQ    $48, SP0x004a 00074 (/home/gofunc/main.go:12)        POPQ    BP0x004b 00075 (/home/gofunc/main.go:12)        RET0x004c 00076 (/home/gofunc/main.go:12)        NOP0x004c 00076 (/home/gofunc/main.go:8) PCDATA  $1, $-10x004c 00076 (/home/gofunc/main.go:8) PCDATA  $0, $-20x004c 00076 (/home/gofunc/main.go:8) MOVQ    AX, 8(SP)0x0051 00081 (/home/gofunc/main.go:8) MOVQ    BX, 16(SP)0x0056 00086 (/home/gofunc/main.go:8) CALL    runtime.morestack_noctxt(SB)0x005b 00091 (/home/gofunc/main.go:8) PCDATA  $0, $-10x005b 00091 (/home/gofunc/main.go:8) MOVQ    8(SP), AX0x0060 00096 (/home/gofunc/main.go:8) MOVQ    16(SP), BX0x0065 00101 (/home/gofunc/main.go:8) JMP     00x0000 49 3b 66 10 76 54 55 48 89 e5 48 83 ec 28 48 89  I;f.vTUH..H..(H.0x0010 44 24 38 48 89 5c 24 40 48 c7 44 24 10 00 00 00  D$8H.\$@H.D$....0x0020 00 48 8d 0c 18 48 89 4c 24 20 48 c7 44 24 18 05  .H...H.L$ H.D$..0x0030 00 00 00 b8 4b 00 00 00 bb 05 00 00 00 0f 1f 00  ....K...........0x0040 e8 00 00 00 00 48 89 44 24 18 48 8b 44 24 20 48  .....H.D$.H.D$ H0x0050 89 44 24 10 48 83 c4 28 5d c3 48 89 44 24 08 48  .D$.H..(].H.D$.H0x0060 89 5c 24 10 e8 00 00 00 00 48 8b 44 24 08 48 8b  .\$......H.D$.H.0x0070 5c 24 10 eb 8b                                   \$...rel 65+4 t=R_CALL main.sub+0rel 101+4 t=R_CALL runtime.morestack_noctxt+0
main.sub STEXT nosplit size=39 args=0x10 locals=0x10 funcid=0x0 align=0x00x0000 00000 (/home/gofunc/main.go:15)        TEXT    main.sub(SB), NOSPLIT|ABIInternal, $16-160x0000 00000 (/home/gofunc/main.go:15)        PUSHQ   BP0x0001 00001 (/home/gofunc/main.go:15)        MOVQ    SP, BP0x0004 00004 (/home/gofunc/main.go:15)        SUBQ    $8, SP0x0008 00008 (/home/gofunc/main.go:15)        FUNCDATA        $0, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB)0x0008 00008 (/home/gofunc/main.go:15)        FUNCDATA        $1, gclocals·FzY36IO2mY0y4dZ1+Izd/w==(SB)0x0008 00008 (/home/gofunc/main.go:15)        FUNCDATA        $5, main.sub.arginfo1(SB)0x0008 00008 (/home/gofunc/main.go:15)        MOVQ    AX, main.x+24(SP)0x000d 00013 (/home/gofunc/main.go:15)        MOVQ    BX, main.y+32(SP)0x0012 00018 (/home/gofunc/main.go:15)        MOVQ    $0, main.~r0(SP)0x001a 00026 (/home/gofunc/main.go:16)        SUBQ    BX, AX0x001d 00029 (/home/gofunc/main.go:16)        MOVQ    AX, main.~r0(SP)0x0021 00033 (/home/gofunc/main.go:16)        ADDQ    $8, SP0x0025 00037 (/home/gofunc/main.go:16)        POPQ    BP0x0026 00038 (/home/gofunc/main.go:16)        RET0x0000 55 48 89 e5 48 83 ec 08 48 89 44 24 18 48 89 5c  UH..H...H.D$.H.\0x0010 24 20 48 c7 04 24 00 00 00 00 48 29 d8 48 89 04  $ H..$....H).H..0x0020 24 48 83 c4 08 5d c3                             $H...].
go:cuinfo.producer.<unlinkable> SDWARFCUINFO dupok size=00x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69              -N -l regabi
go:cuinfo.packagename.main SDWARFCUINFO dupok size=00x0000 6d 61 69 6e                                      main
main..inittask SNOPTRDATA size=80x0000 00 00 00 00 00 00 00 00                          ........
gclocals·FzY36IO2mY0y4dZ1+Izd/w== SRODATA dupok size=80x0000 01 00 00 00 00 00 00 00                          ........
main.add.arginfo1 SRODATA static dupok size=50x0000 00 08 08 08 ff                                   .....
main.sub.arginfo1 SRODATA static dupok size=50x0000 00 08 08 08 ff                                   .....

我们对add函数汇编结果的前两行最分析(其他的内容用dlv分析,结果更直观):

1) 函数头信息 (STEXT)

main.add STEXT size=103 args=0x10 locals=0x38 funcid=0x0 align=0x0

  • ​**STEXT**
    表示该段代码属于程序的代码段(Text Segment),用于存放可执行指令。
  • ​**size=117**
    函数内容编译后的机器码总大小为 103 字节。
  • ​**args=0x10**
    参数和返回值总大小为 16 字节(对应两个 int64 类型,8+8)。
  • ​**locals=0x38**
    局部变量和临时存储空间占用 56 字节(包含寄存器值、栈扩展保留空间等)。
  • ​**funcid=0x0**
    函数标识符,0 表示普通函数(非闭包或方法)。
  • ​**align=0x0**
    函数入口地址对齐方式,0 表示使用默认对齐(通常为 16 字节)。

2)函数定义 (TEXT)

0x0000 00000 (/home/gofunc/main.go:8) TEXT    main.add(SB), ABIInternal, $56-16

  • ​**main.add(SB)**
    定义函数 main.addSB 是虚拟的静态基址寄存器,表示符号的全局地址。
  • ​**ABIInternal**
    使用 Go 1.17+ 的内部调用约定(Internal ABI),通过寄存器传递参数,提升性能。
  • ​**$56-16**
    • 56:函数栈帧总大小(单位字节)。
    • 16:参数和返回值的总大小(由调用者在栈上分配)。

PS:这里可能会疑惑为什么传参和返回值怎么才16字节,不应该34字节吗?这是因为go编译器编译时让返回值复用一个参数的内存了。

1.2、dlv debug

 go汇编,汇编指令解析放在每一行最后。

[root@test gofunc]# dlv debug main.go
Type 'help' for list of commands.
(dlv) b main.go:3
Breakpoint 1 set at 0x470aea for main.main() ./main.go:3
(dlv) b main.go:8
Breakpoint 2 set at 0x470b4a for main.add() ./main.go:8
(dlv) b main.go:15
Breakpoint 3 set at 0x470bc4 for main.sub() ./main.go:15
(dlv) c
> [Breakpoint 1] main.main() ./main.go:3 (hits goroutine(1):1 total:1) (PC: 0x470aea)1: package main2:
=>   3: func main() {4:     x,y:=int64(1),int64(2)5:     x=add(x,y)6: }7:8: func add(a, b int64) int64 {
(dlv) disass
TEXT main.main(SB) /home/gofunc/main.gomain.go:3       0x470ae0        493b6610                cmp rsp, qword ptr [r14+0x10] //比较栈顶(rsp)和协程栈预警值(g.stackguard0)大小,小于则要栈扩容main.go:3       0x470ae4        7634                    jbe 0x470b1a//小于成立,则跳到0x470b9a指令处,进行栈扩容main.go:3       0x470ae6        55                      push rbp //入栈main.go:3       0x470ae7        4889e5                  mov rbp, rsp
=>      main.go:3       0x470aea*       4883ec20                sub rsp, 0x20//SP寄存器值减去32,表示栈顶向下减小32字节,即为main函数申请32字节的栈内存保存其上下文main.go:4       0x470aee        48c744241801000000      mov qword ptr [rsp+0x18], 0x1 //设置x变量的值,占用8字节,即其占用rsp(栈顶)向上[0x18,0x20]之间的内存,第24到32字节之间main.go:4       0x470af7        48c744241002000000      mov qword ptr [rsp+0x10], 0x2  //设置y变量的值,占用8字节,即其占用rsp(栈顶)向上[0x10,0x18]之间的内存,第16到24字节之间main.go:5       0x470b00        b801000000              mov eax, 0x1 //设置AX寄存器值为1,即调用add的参数xmain.go:5       0x470b05        bb02000000              mov ebx, 0x2 //设置BX寄存器值为2,即调用add的参数ymain.go:5       0x470b0a        e831000000              call $main.add //调用add函数main.go:5       0x470b0f        4889442418              mov qword ptr [rsp+0x18], rax //将add返回值从AX寄存器中取出来,赋值给x变量main.go:6       0x470b14        4883c420                add rsp, 0x20//SP寄存器值加上32,表示栈顶向上增加32字节,即将main函数申请32字节的栈内存归还main.go:6       0x470b18        5d                      pop rbp //出栈main.go:6       0x470b19        c3                      retmain.go:3       0x470b1a        e8c1adffff              call $runtime.morestack_noctxtmain.go:3       0x470b1f        90                      nopmain.go:3       0x470b20        ebbe                    jmp $main.main
(dlv) c
> [Breakpoint 2] main.add() ./main.go:8 (hits goroutine(1):1 total:1) (PC: 0x470b4a)3: func main() {4:     x,y:=int64(1),int64(2)5:     x=add(x,y)6: }7:
=>   8: func add(a, b int64) int64 {9:     c:=a+b10:     q:=int64(5)11:     p:=sub(c, q)12:     return p13: }
(dlv) disass
TEXT main.add(SB) /home/gofunc/main.gomain.go:8       0x470b40        493b6610                cmp rsp, qword ptr [r14+0x10]  main.go:8       0x470b44        7654                    jbe 0x470b9a main.go:8       0x470b46        55                      push rbp //将BP寄存器的值入栈,此时BP寄存器存的是main函数栈帧起始位置的地址,此时SP值等于SP值减去8字节,用于存储BP寄存器的值main.go:8       0x470b47        4889e5                  mov rbp, rsp//将SP寄存器值赋给BP寄存器,作为add函数栈帧的栈底
=>      main.go:8       0x470b4a*       4883ec28                sub rsp, 0x30//SP寄存器值减去48,表示栈顶向下减少48字节,即为add函数申请48字节的栈内存保存其上下文main.go:8       0x470b4e        4889442438              mov qword ptr [rsp+0x40], rax //从AX寄存器取参数x的值,放到rsp+0x38地址处,使用的是main函数的栈main.go:8       0x470b53        48895c2440              mov qword ptr [rsp+0x48], rbx//从BX寄存器取参数y的值,放到rsp+0x40地址处,使用的是main函数的栈main.go:8       0x470b38        48c744241000000000      mov qword ptr [rsp+0x10], 0x0 //令rsp+0x10地址处的值为0,即rsp+[0x10,0x18]之间的内存处存储的值清空main.go:9       0x470b41        4801d8                  add rax, rbx//令AX寄存器值等于AX寄存器值+BX寄存器值,即c=a+b的操作main.go:9       0x470b44        4889442428              mov qword ptr [rsp+0x28], rax //将AX寄存器值放到rsp+0x28地址处main.go:10      0x470b6a        48c744241805000000      mov qword ptr [rsp+0x18], 0x5 //令rsp+0x18地址处值为5,即q:=int64(5)main.go:11      0x470b78        bb05000000              mov ebx, 0x5//令AX寄存器值等与0x5(5),为调用sub函数第二个参数main.go:11      0x470b7d        0f1f00                  nop dword ptr [rax], eaxmain.go:11      0x470b80        e83b000000              call $main.sub //调用sub本函数main.go:11      0x470b5c        4889442420              mov qword ptr [rsp+0x20], rax //将sub函数返回结果放到rsp+0x20地址处main.go:12      0x470b8f        4889442410              mov qword ptr [rsp+0x10], rax //令rsp+0x10地址处的值等与AX寄存器的值(莫名其妙的操作,大神知道这个操作做啥吗,评论区见)main.go:12      0x470b94        4883c428                add rsp, 0x30//SP寄存器值加上48,表示栈顶向上增加48字节,即将add函数申请48字节的栈内存归还main.go:12      0x470b98        5d                      pop rbp //出栈,即将栈顶值(此时其值是main函数栈帧起始位置的地址)设置给BP寄存器main.go:12      0x470b99        c3                      ret//返回到main函数调用add函数处继续执行main.go:8       0x470b9a        4889442408              mov qword ptr [rsp+0x8], rax //栈扩容前保存下AX寄存器的值,即参数a,因为栈扩容会覆盖AX寄存器的值main.go:8       0x470b9f        48895c2410              mov qword ptr [rsp+0x10], rbx//栈扩容前保存下BX寄存器的值,即参数b,因为栈扩容会覆盖BX寄存器的值main.go:8       0x470ba4        e837adffff              call $runtime.morestack_noctxt  //栈扩容main.go:8       0x470ba9        488b442408              mov rax, qword ptr [rsp+0x8] //扩容后重新设置参数a到AX寄存器main.go:8       0x470bae        488b5c2410              mov rbx, qword ptr [rsp+0x10] //扩容后重新设置参数b到BX寄存器main.go:8       0x470bb3        eb8b                    jmp $main.add //扩容后重新跳到add函数代码段(不是调用add函数),继续执行add函数
(dlv) c
> [Breakpoint 3] main.sub() ./main.go:15 (hits goroutine(1):1 total:1) (PC: 0x470bc4)10:     q:=int64(5)11:     q=sub(75, q)12:     return c13: }14:
=>  15: func sub(x, y int64) int64{16:   return x -y17: }
(dlv) disass
TEXT main.sub(SB) /home/gofunc/main.gomain.go:15      0x470bc0        55                      push rbpmain.go:15      0x470bc1        4889e5                  mov rbp, rsp
=>      main.go:15      0x470bc4*       4883ec08                sub rsp, 0x8//申请8字节栈内存main.go:15      0x470bc8        4889442418              mov qword ptr [rsp+0x18], raxmain.go:15      0x470bcd        48895c2420              mov qword ptr [rsp+0x20], rbxmain.go:15      0x470bd2        48c7042400000000        mov qword ptr [rsp], 0x0//令rsp地址处的值为0main.go:16      0x470bda        4829d8                  sub rax, rbx //计算x-y的值并将结果存到AX寄存器中,这样add函数从AX寄存器取sub函数返回值main.go:16      0x470bdd        48890424                mov qword ptr [rsp], rax//令rsp地址处的值为AX寄存器的值,也就是x-y的值main.go:16      0x470be1        4883c408                add rsp, 0x8 //归还8字节栈内存main.go:16      0x470be5        5d                      pop rbp//出栈,BP寄存器值恢复到add函数栈帧起始位置地址main.go:16      0x470be6        c3                      ret

通过观察三个函数的汇编,可以看到【栈的申请与归还操作】和【函数栈帧的恢复】:
栈申请:

        push rbp //入栈操作等价于【sub rsp 0x8;  mov rsp, rbp】,即申请8字节栈内存用于保存caller的函数栈帧起始位置地址
        mov rbp, rsp //保存callee的函数栈帧起始位置地址
        sub rsp, 0x28 //栈申请

栈归还:

        add rsp, 0x28  //栈归还
        pop rbp //出栈操作等价于【mov rbp, rsp; add rsp 0x8】,恢复caller的函数栈帧起始位置地址

 二、栈布局与函数栈帧

通过第一章对示例的汇编进行分析,我们可以画出其栈布局,假设main函数被调用时的起始地址是ox3e8(1000),那么我们可以得到下图:

 可以看到整体还是符合go源码给出的栈布局的:

// (x86)
// +------------------+
// | args from caller |
// +------------------+ <- frame->argp
// |  return address  |
// +------------------+
// |  caller's BP (*) | (*) if framepointer_enabled && varp > sp (暂存调用者函数栈帧起始位置地址)
// +------------------+ <- frame->varp (函数栈帧起始位置地址 BP寄存器)
// |     locals       |
// +------------------+
// |  args to callee  |
// +------------------+ <- frame->sp (栈顶,SP寄存器)

 一个函数栈帧由BP寄存器和SP寄存器确定。

C中函数栈帧是逐步扩张的(每定义一个变量就扩张一次), 但是go里面函数栈帧的扩张是一次性分配一大块(直接将栈指针移动到所需最大栈空间的位置,即栈顶),然后通过栈顶指针加偏移量这种相对寻址方式使用函数栈帧。

函数栈帧大小在编译期就可以确定!!! 对于栈消耗较大的函数,编译器会在函数头部插入检测代码,如果发现需要进行“栈扩容”,就会调用runtime.morestack相关函数重新分配一块足够大的栈空间, 将原来的数据拷过来并释放原来的空间。

http://www.dtcms.com/wzjs/353736.html

相关文章:

  • 网站制作设计济南网站建设制作
  • 东莞网站建设 旅游百度收录提交入口
  • 地下城做解封任务的网站站长统计网站统计
  • 做ppt比较好的网站有哪些济南新站seo外包
  • 咸宁网站建设价格百度站长平台怎么用
  • 做执法设备有哪些网站google chrome官网下载
  • wordpress 网站打开速度慢百度购物平台客服电话
  • 个人做网站外包价格如何算企业推广宣传方式
  • 网站建设培训西安如何刷app推广次数
  • p2p网贷网站建设方案赛雷猴是什么意思
  • 网站开发咨询郑州网
  • 南宁在哪里可以做网站百度信息流优化
  • 新手做导航网站数据指数
  • 网站开发做美工服务之家网站推广
  • 怎么做免费网站如何让百度收录网站优化员seo招聘
  • 如何用php数据库做网站网络服务有哪些
  • 苏州好的做网站的公司推广电话
  • wordpress 怎么修改郑州百度seo网站优化
  • 无需下载的网站百度网盘seo优化
  • 网站首页导航栏广州seo好找工作吗
  • 西安网站建设怎样友情下载网站
  • 贵阳设计公司怎么seo快速排名
  • 有没有做软件的网站佛山网站建设方案咨询
  • 网站备案备注西安seo阳建
  • 莱芜网站建设怎么样进一步优化落实
  • 网络公司给我们做的网站_但是我们不知道域名是否属于我们武汉seo优化顾问
  • 阳江房产网官网查询长沙seo行者seo09
  • 政府网站建设的理论依据发稿软文公司
  • 小红书怎么推广自己的产品优化网站内容的方法
  • 精品网站建设公司手机怎么制作网页