zoukankan      html  css  js  c++  java
  • 14 局部段描述符的使用

    参考

    https://www.cnblogs.com/wanmeishenghuo/tag/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/

    https://blog.51cto.com/13475106/category6.html

    前面我们使用的都是全局段描述符表,现在我们来分析局部段描述符表的使用。

     什么是LDT(Local Descriptor Table)?

      局部段描述符表:

        本质是一个段描述符表,用于定义段描述符

        与GDT类似,可以看做“段描述符的数组”

        通过定义选择子访问局部段描述符表中的元素

    局部段描述符的选择子和全局描述符的选择子在结构上是完全一样的,3-15位为描述符索引值,LDT选择子的第二位恒为1,1-0位为RPL。

    局部段描述符表就是一段内存,里面的每一项是一个局部段描述符(第0项也是有意义的),用于描述一段内存。

    CPU中有一个专用寄存器专门指向局部段描述符表。(先定义一个常量   DA_LDT    equ  0x82),在全局段描述符表中定义局部段描述符表的描述项时,需要用到属性,这个属性就是DA_LDT。

     局部段描述符表的注意事项:

      局部段描述符表需要在全局段描述符表中注册(增加描述项)

      通过对应的选择子加载局部段描述符(lldt)

      局部段描述符表从第0项开始使用(different  from  GDT)

    LDT的意义:

      代码层面的意义:

        分级管理功能相同意义不同的段(如:多个代码段),全局段描述符表也是有界限的,如果分段过多,则全局段描述符表有可能不够用。而局部段描述符表不限制描述符的个数。引入这种分级管理描述符的机制,可以定义无数个段。

      系统层面的意义:

        实现多任务的基础要素(每个任务对应一系列不同的段)

     LDT的定义与使用:

      1、定义独立功能相关的段(代码段、数据段、栈段)

      2、将目标段描述符组成局部段描述符表(LDT)

      3、为各个段描述符定义选择子(SA_TIL)

      4、在GDT中定义LDT的段描述符,并定义选择子

     下面给出示例程序,演示局部段描述符的使用。

    inc.asm更新如下:

    ; Segment Attribute
    DA_32    equ    0x4000
    DA_DR    equ    0x90
    DA_DRW   equ    0x92
    DA_DRWA  equ    0x93
    DA_C     equ    0x98
    DA_CR    equ    0x9A
    DA_CCO   equ    0x9C
    DA_CCOR  equ    0x9E
    
    ; Special Attribute
    DA_LDT   equ    0x82
    
    ; Selector Attribute
    SA_RPL0    equ    0
    SA_RPL1    equ    1
    SA_RPL2    equ    2
    SA_RPL3    equ    3
    
    SA_TIG    equ    0
    SA_TIL    equ    4
    
    ; 描述符
    ; usage: Descriptor Base, Limit, Attr
    ;        Base:  dd
    ;        Limit: dd (low 20 bits available)
    ;        Attr:  dw (lower 4 bits of higher byte are always 0)
    %macro Descriptor 3	                          ; 段基址, 段界限, 段属性
        dw    %2 & 0xFFFF                         ; 段界限1
        dw    %1 & 0xFFFF                         ; 段基址1
        db    (%1 >> 16) & 0xFF                   ; 段基址2
        dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
        db    (%1 >> 24) & 0xFF                   ; 段基址3
    %endmacro                                     ; 共 8 字节
    

     

    我们增加了第12行的宏定义。

    loader.asm的程序如下:

    %include "inc.asm"
    
    org 0x9000
    
    jmp ENTRY_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0
    CODE32_DESC     :     Descriptor    0,    Code32SegLen  - 1,   DA_C + DA_32
    VIDEO_DESC      :     Descriptor 0xB8000,       0x07FFF,       DA_DRWA + DA_32
    DATA32_DESC     :     Descriptor    0,    Data32SegLen  - 1,   DA_DR + DA_32
    STACK32_DESC    :     Descriptor    0,      TopOfStack32,      DA_DRW + DA_32
    CODE16_DESC     :     Descriptor    0,           0xFFFF,       DA_C
    UPDATE_DESC     :     Descriptor    0,           0xFFFF,       DA_DRW
    TASK_A_LDT_DESC   :     Descriptor    0,       TaskALdtLen - 1,  DA_LDT
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
    
    
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
    Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
    Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
    Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
    UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
    TaskALdtSelector  equ (0x0007 << 3) + SA_TIG + SA_RPL0
    ; end of [section .gdt]
    
    TopOfStack16    equ  0x7c00
    
    [section .dat]
    [bits 32]
    DATA32_SEGMENT:
        DTOS                 db    "D.T.OS!", 0
        DTOS_OFFSET          equ   DTOS - $$
        HELLO_WORLD          db    "Hello World!", 0
        HELLO_WORLD_OFFSET   equ  HELLO_WORLD - $$
    
    Data32SegLen  equ $ - DATA32_SEGMENT
    
    [section .s16]
    [bits 16]
    ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
    
        mov [BACK_TO_REAL_MODE + 3], ax
    
        ; initialize GDT for 32 bits code segment
        mov esi, CODE32_SEGMENT
        mov edi, CODE32_DESC
    
        call InitDescItem
    
        mov esi, DATA32_SEGMENT
        mov edi, DATA32_DESC
    
        call InitDescItem
    
        mov esi, DATA32_SEGMENT
        mov edi, STACK32_DESC
    
        call InitDescItem
    
        mov esi, CODE16_SEGMENT
        mov edi, CODE16_DESC
    
        call InitDescItem
    
        mov esi, TASK_A_LDT_ENTRY
        mov edi, TASK_A_LDT_DESC
    
        call InitDescItem
    
        mov esi, TASK_A_CODE32_SEGMENT
        mov edi, TASK_A_CODE32_DESC
    
        call InitDescItem
    
        mov esi, TASK_A_DATA32_SEGMENT
        mov edi, TASK_A_DATA32_DESC
    
        call InitDescItem
    
        mov esi, TASK_A_STACK32_SEGMENT
        mov edi, TASK_A_STACK32_DESC
    
        call InitDescItem
    
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
    
        ; 2. close interrupt
        cli
    
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
    
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01
        mov cr0, eax
    
        ; 5. jump to 32 bits code
        jmp dword Code32Selector : 0
    
    BACK_ENTRY_SEGMENT:
            mov ax, cs
            mov ds, ax
            mov es, ax
            mov ss, ax
            mov sp, TopOfStack16
    
            in al, 0x92
            and al, 11111101b
            out 0x92, al
    
            sti
    
            mov bp, HELLO_WORLD
            mov cx, 12
            mov dx, 0
            mov ax, 0x1301
            mov bx, 0x0007
            int 0x10
    
            jmp $
    
    ; esi    --> code segment label
    ; edi    --> descriptor label
    InitDescItem:
        push eax
    
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, esi
        mov word [edi + 2], ax
        shr eax, 16
        mov byte [edi + 4], al
        mov byte [edi + 7], ah
    
        pop eax
    
        ret
    
    
    [section .16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, UpdateSelector
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
    
        mov eax, cr0
        and al, 11111110b
        mov cr0, eax
    
    BACK_TO_REAL_MODE:
        jmp 0 : BACK_ENTRY_SEGMENT
    
    Code16SegLen    equ $ - CODE16_SEGMENT
    
    
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
    
        mov ax, Stack32Selector
        mov ss, ax
    
        mov eax, TopOfStack32
        mov esp, eax
    
        mov ax, Data32Selector
        mov ds, ax
    
        mov ebp, DTOS_OFFSET
        mov bx, 0x0C
        mov dh, 12
        mov dl, 33
    
        call PrintString
    
        mov ebp, HELLO_WORLD_OFFSET
        mov bx, 0x0C
        mov dh, 13
        mov dl, 30
    
        call PrintString
    
        mov ax, TaskALdtSelector
        lldt ax
    
        jmp TaskACode32Selector : 0
    
        ;jmp Code16Selector : 0
    
    ; ds:ebp   --> string address
    ; bx       --> attribute
    ; dx       --> dh : row, dl : col
    PrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
    
    print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp print
    
    end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
    
        ret
    
    Code32SegLen    equ    $ - CODE32_SEGMENT
    
    [section .gs]
    [bits 32]
    STACK32_SEGMENT:
        times 1014 * 4 db 0
    
    Stack32SegLen    equ $ - STACK32_SEGMENT
    TopOfStack32    equ Stack32SegLen - 1
    
    
    ; ==================================
    ;
    ;        Task A Code Segment
    ;
    ;===================================
    [section .task-a-ldt]
    ; Task A LDT definition
    ;                                        段基址                     段界限                段属性
    
    TASK_A_LDT_ENTRY:
    TASK_A_CODE32_DESC    :   Descriptor       0,               TaskACode32SegLen - 1,  DA_C + DA_32
    TASK_A_DATA32_DESC    :   Descriptor       0,               TaskAData32SegLen - 1,  DA_DR + DA_32
    TASK_A_STACK32_DESC   :   Descriptor       0,               TaskAStack32SegLen - 1, DA_DRW + DA_32
    
    TaskALdtLen        equ   $ - TASK_A_LDT_ENTRY
    
    ; Task A LDT  Selector
    TaskACode32Selector        equ  (0x0000 << 3) + SA_TIL + SA_RPL0
    TaskAData32Selector     equ     (0x0001 << 3) + SA_TIL + SA_RPL0
    TaskAStack32Selector    equ  (0x0002 << 3) + SA_TIL + SA_RPL0
    
    [section .task-a-dat]
    [bits 32]
    TASK_A_DATA32_SEGMENT:
        TASK_A_STRING        db   "This is Task A", 0
        TASK_STRING_OFFSET    equ     TASK_A_STRING - $$
    
    TaskAData32SegLen    equ $ - TASK_A_DATA32_SEGMENT
    
    [section .task-a-gs]
    [bits 32]
    TASK_A_STACK32_SEGMENT:
        times 1024 db 0
    
    TaskAStack32SegLen    equ     $ - TASK_A_STACK32_SEGMENT
    TaskATopOfStack32    equ     TaskAStack32SegLen - 1
    
    [section .task-a-s32]
    [bits 32]
    TASK_A_CODE32_SEGMENT:
    
        mov ax, VideoSelector
        mov gs, ax
    
        mov ax, TaskAStack32Selector
        mov ss, ax
    
        mov eax, TaskATopOfStack32
        mov esp, eax
    
        mov ax, TaskAData32Selector
        mov ds, ax
    
        jmp $
    
    TaskACode32SegLen     equ  $ - TASK_A_CODE32_SEGMENT
    

    从271行开始,我们定义了代表LDT段描述符表本身的段,定义了LDT下的代码段、数据段、栈段。在代码段中,我们加载了相应的段寄存器,最终程序停在原地。

    在第17行为LDT段描述符表本身占用的内存增加了段描述符项,用来描述这段内存。

    第35行增加了LDT段描述符表所在段的选择子。

    218-221行,我们添加了三行程序,作用为加载LDT段描述符表,跳转到LDT段描述符表描述的代码段去执行。

    执行结果如下:

     

    我们在LDT描述的代码段中加入以下打印程序:

    在324-329行加入了打印字符串的功能,这个PrintString函数是在全局段描述符表的代码段中定义的。

    执行结果如下:

    我们看到发生了CPU硬件复位。

    因为PrintString函数不是属于LDT中的代码段的,我们直接调用它导致了越界,从而CPU复位。因为现在的选择子是LDT段描述符中的选择子,即使PrintString代表的偏移地址是相同的也会发生错误。

    我们将打印相关的函数复制一份到LDT描述符下的代码段中,如下:

    %include "inc.asm"
    
    org 0x9000
    
    jmp ENTRY_SEGMENT
    
    [section .gdt]
    ; GDT definition
    ;                                 段基址,       段界限,       段属性
    GDT_ENTRY       :     Descriptor    0,            0,           0
    CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32
    VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32
    DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32
    STACK32_DESC    :     Descriptor    0,     TopOfStack32,       DA_DRW + DA_32
    CODE16_DESC     :     Descriptor    0,        0xFFFF,          DA_C 
    UPDATE_DESC     :     Descriptor    0,        0xFFFF,          DA_DRW
    TASK_A_LDT_DESC :     Descriptor    0,     TaskALdtLen - 1,    DA_LDT
    ; GDT end
    
    GdtLen    equ   $ - GDT_ENTRY
    
    GdtPtr:
              dw   GdtLen - 1
              dd   0
              
              
    ; GDT Selector
    
    Code32Selector    equ (0x0001 << 3) + SA_TIG + SA_RPL0
    VideoSelector     equ (0x0002 << 3) + SA_TIG + SA_RPL0
    Data32Selector    equ (0x0003 << 3) + SA_TIG + SA_RPL0
    Stack32Selector   equ (0x0004 << 3) + SA_TIG + SA_RPL0
    Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
    UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
    TaskALdtSelector  equ (0x0007 << 3) + SA_TIG + SA_RPL0
    ; end of [section .gdt]
    
    TopOfStack16    equ 0x7c00
    
    [section .dat]
    [bits 32]
    DATA32_SEGMENT:
        DTOS               db  "D.T.OS!", 0
        DTOS_OFFSET        equ DTOS - $$
        HELLO_WORLD        db  "Hello World!", 0
        HELLO_WORLD_OFFSET equ HELLO_WORLD - $$
    
    Data32SegLen equ $ - DATA32_SEGMENT
    
    [section .s16]
    [bits 16]
    ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
        
        mov [BACK_TO_REAL_MODE + 3], ax
        
        ; initialize GDT for 32 bits code segment
        mov esi, CODE32_SEGMENT
        mov edi, CODE32_DESC
        
        call InitDescItem
        
        mov esi, DATA32_SEGMENT
        mov edi, DATA32_DESC
        
        call InitDescItem
        
        mov esi, STACK32_SEGMENT
        mov edi, STACK32_DESC
        
        call InitDescItem
        
        mov esi, CODE16_SEGMENT
        mov edi, CODE16_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_LDT_ENTRY
        mov edi, TASK_A_LDT_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_CODE32_SEGMENT
        mov edi, TASK_A_CODE32_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_DATA32_SEGMENT
        mov edi, TASK_A_DATA32_DESC
        
        call InitDescItem
        
        mov esi, TASK_A_STACK32_SEGMENT
        mov edi, TASK_A_STACK32_DESC
        
        call InitDescItem
        
        ; initialize GDT pointer struct
        mov eax, 0
        mov ax, ds
        shl eax, 4
        add eax, GDT_ENTRY
        mov dword [GdtPtr + 2], eax
    
        ; 1. load GDT
        lgdt [GdtPtr]
        
        ; 2. close interrupt
        cli 
        
        ; 3. open A20
        in al, 0x92
        or al, 00000010b
        out 0x92, al
        
        ; 4. enter protect mode
        mov eax, cr0
        or eax, 0x01
        mov cr0, eax
        
        ; 5. jump to 32 bits code
        jmp dword Code32Selector : 0
    
    BACK_ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
        
        in al, 0x92
        and al, 11111101b
        out 0x92, al
        
        sti
        
        mov bp, HELLO_WORLD
        mov cx, 12
        mov dx, 0
        mov ax, 0x1301
        mov bx, 0x0007
        int 0x10
        
        jmp $
    
    ; esi    --> code segment label
    ; edi    --> descriptor label
    InitDescItem:
        push eax
    
        mov eax, 0
        mov ax, cs
        shl eax, 4
        add eax, esi
        mov word [edi + 2], ax
        shr eax, 16
        mov byte [edi + 4], al
        mov byte [edi + 7], ah
        
        pop eax
        
        ret
        
    
    [section .s16]
    [bits 16]
    CODE16_SEGMENT:
        mov ax, UpdateSelector
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        
        mov eax, cr0
        and al, 11111110b
        mov cr0, eax
    
    BACK_TO_REAL_MODE:    
        jmp 0 : BACK_ENTRY_SEGMENT
        
    Code16SegLen    equ    $ - CODE16_SEGMENT
    
        
    [section .s32]
    [bits 32]
    CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
        
        mov ax, Stack32Selector
        mov ss, ax
        
        mov eax, TopOfStack32
        mov esp, eax
        
        mov ax, Data32Selector
        mov ds, ax
        
        mov ebp, DTOS_OFFSET
        mov bx, 0x0C
        mov dh, 12
        mov dl, 33
        
        call PrintString
        
        mov ebp, HELLO_WORLD_OFFSET
        mov bx, 0x0C
        mov dh, 13
        mov dl, 31
        
        call PrintString
        
        mov ax, TaskALdtSelector
        
        lldt ax
        
        jmp TaskACode32Selector : 0
        
        ; jmp Code16Selector : 0
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    PrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp print
    
    end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    Code32SegLen    equ    $ - CODE32_SEGMENT
    
    [section .gs]
    [bits 32]
    STACK32_SEGMENT:
        times 1024 * 4 db 0
        
    Stack32SegLen equ $ - STACK32_SEGMENT
    TopOfStack32  equ Stack32SegLen - 1
    
    
    ; ==========================================
    ;
    ;            Task A Code Segment 
    ;
    ; ==========================================
    
    [section .task-a-ldt]
    ; Task A LDT definition
    ;                                             段基址,                段界限,                段属性
    TASK_A_LDT_ENTRY:
    TASK_A_CODE32_DESC    :    Descriptor          0,           TaskACode32SegLen - 1,        DA_C + DA_32
    TASK_A_DATA32_DESC    :    Descriptor          0,           TaskAData32SegLen - 1,        DA_DR + DA_32
    TASK_A_STACK32_DESC   :    Descriptor          0,           TaskAStack32SegLen - 1,       DA_DRW + DA_32
    
    TaskALdtLen  equ   $ - TASK_A_LDT_ENTRY
    
    ; Task A LDT Selector
    TaskACode32Selector  equ   (0x0000 << 3) + SA_TIL + SA_RPL0
    TaskAData32Selector  equ   (0x0001 << 3) + SA_TIL + SA_RPL0
    TaskAStack32Selector equ   (0x0002 << 3) + SA_TIL + SA_RPL0
    
    [section .task-a-dat]
    [bits 32]
    TASK_A_DATA32_SEGMENT:
        TASK_A_STRING        db   "This is Task A!", 0
        TASK_A_STRING_OFFSET equ  TASK_A_STRING - $$
        
    TaskAData32SegLen  equ  $ - TASK_A_DATA32_SEGMENT
    
    [section .task-a-gs]
    [bits 32]
    TASK_A_STACK32_SEGMENT:
        times 1024 db 0
        
    TaskAStack32SegLen  equ  $ - TASK_A_STACK32_SEGMENT
    TaskATopOfStack32   equ  TaskAStack32SegLen - 1
    
    [section .task-a-s32]
    [bits 32]
    TASK_A_CODE32_SEGMENT:
        mov ax, VideoSelector
        mov gs, ax
        
        mov ax, TaskAStack32Selector
        mov ss, ax
        
        mov eax, TaskATopOfStack32
        mov esp, eax
        
        mov ax, TaskAData32Selector
        mov ds, ax
        
        mov ebp, TASK_A_STRING_OFFSET
        mov bx, 0x0C
        mov dh, 14
        mov dl, 29
        
        call TaskAPrintString
        
        jmp Code16Selector : 0
        
    
    ; ds:ebp    --> string address
    ; bx        --> attribute
    ; dx        --> dh : row, dl : col
    TaskAPrintString:
        push ebp
        push eax
        push edi
        push cx
        push dx
        
    task_print:
        mov cl, [ds:ebp]
        cmp cl, 0
        je task_end
        mov eax, 80
        mul dh
        add al, dl
        shl eax, 1
        mov edi, eax
        mov ah, bl
        mov al, cl
        mov [gs:edi], ax
        inc ebp
        inc dl
        jmp task_print
    
    task_end:
        pop dx
        pop cx
        pop edi
        pop eax
        pop ebp
        
        ret
        
    TaskACode32SegLen   equ  $ - TASK_A_CODE32_SEGMENT
    

     

    我们复制了打印函数,在第332行跳转到16位的保护模式,进一步跳转到16位实模式。

    执行结果如下:

     

    小结:

    局部段描述符表用于组织功能相关的段

    局部段描述符表需要加载后才能正常使用

    局部段描述符表必须在全局段描述符表中注册

    通过局部段描述符表的选择子对其进行访问

    局部段描述符表时实现多任务的基础

     

      

     

  • 相关阅读:
    给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
    11
    实战 迁移学习 VGG19、ResNet50、InceptionV3 实践 猫狗大战 问题
    tx2系统备份与恢复
    如何在Ubuntu 18.04上安装和卸载TeamViewer
    bzoj 3732 Network (kruskal重构树)
    bzoj2152 聪聪可可 (树形dp)
    牛客 216D 消消乐 (二分图最小点覆盖)
    牛客 197E 01串
    Wannafly挑战赛23
  • 原文地址:https://www.cnblogs.com/lh03061238/p/14096048.html
Copyright © 2011-2022 走看看