程序如何得到特定符号的所在物理地址

Posted w-smile

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序如何得到特定符号的所在物理地址相关的知识,希望对你有一定的参考价值。

  在移植u-boot的过程看到过u-boot在重定向时的实现,当时不知道怎么就觉得很好理解就把这个知识点没怎么深入的理解,最近在看华为的鸿蒙OS在Cortex-A平台上的实现过程时再次遇到一时间看不太懂了,所以花了点时间研究了一下这里做一下记录,后续有时间再把u-boot的实现再复盘一下加深理解。具体的代码如下

  1 /*
  2  * Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.
  3  * Copyright (c) 2020, Huawei Device Co., Ltd. All rights reserved.
  4  *
  5  * Redistribution and use in source and binary forms, with or without modification,
  6  * are permitted provided that the following conditions are met:
  7  *
  8  * 1. Redistributions of source code must retain the above copyright notice, this list of
  9  *    conditions and the following disclaimer.
 10  *
 11  * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 12  *    of conditions and the following disclaimer in the documentation and/or other materials
 13  *    provided with the distribution.
 14  *
 15  * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 16  *    to endorse or promote products derived from this software without specific prior written
 17  *    permission.
 18  *
 19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 22  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 26  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 28  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 29  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 30  */
 31 
 32 #define  ASSEMBLY
 33 #include "arch_config.h"
 34 #include "los_vm_boot.h"
 35 #include "los_vm_zone.h"
 36 #include "los_mmu_descriptor_v6.h"
 37 #undef ASSEMBLY
 38 
 39 
 40     .global __exc_stack_top
 41     .global __irq_stack_top
 42     .global __fiq_stack_top
 43     .global __svc_stack_top
 44     .global __abt_stack_top
 45     .global __undef_stack_top
 46     .global __exc_stack
 47     .global __irq_stack
 48     .global __fiq_stack
 49     .global __svc_stack
 50     .global __abt_stack
 51     .global __undef_stack
 52 
 53     .extern __bss_start
 54     .extern __bss_end
 55     .extern hal_clock_initialize_start
 56     .extern los_bss_init
 57     .extern _osExceptFiqHdl
 58     .extern _osExceptAddrAbortHdl
 59     .extern _osExceptDataAbortHdl
 60     .extern _osExceptPrefetchAbortHdl
 61     .extern _osExceptSwiHdl
 62     .extern _osExceptUndefInstrHdl
 63     .extern __stack_chk_guard_setup
 64     .extern g_firstPageTable
 65     .extern g_mmuJumpPageTable
 66 
 67     .equ MPIDR_CPUID_MASK, 0xffU
 68 
 69     .fpu vfpv4
 70     .arm
 71 
 72 /* param0 is stack bottom, param1 is stack size, r11 hold cpu id */
 73 .macro EXC_SP_SET param0, param1
 74     ldr    r1, =param0
 75     mov    r0, param1
 76     bl     sp_set
 77 .endm
 78 
 79 /* param0 is stack top, param1 is stack size, param2 is magic num */
 80 .macro STACK_MAGIC_SET param0, param1, param2
 81     ldr     r0, =param0
 82     mov     r1, param1
 83     ldr     r2, =param2
 84     bl      excstack_magic
 85 .endm
 86 
 87 /* param0 is physical address, param1 virtual address, param2 is sizes, param3 is flag */
 88 .macro PAGE_TABLE_SET param0, param1, param2, param3
 89     ldr     r6, =param0
 90     ldr     r7, =param1
 91     ldr     r8, =param2
 92     ldr     r10, =param3
 93     bl      page_table_build
 94 .endm
 95     .code   32
 96     .section ".vectors","ax"
 97 
 98 __exception_handlers:
 99     /*
100     *Assumption:  ROM code has these vectors at the hardware reset address.
101     *A simple jump removes any address-space dependencies [i.e. safer]
102     */
103     b   reset_vector
104     b   _osExceptUndefInstrHdl
105     b   _osExceptSwiHdl
106     b   _osExceptPrefetchAbortHdl
107     b   _osExceptDataAbortHdl
108     b   _osExceptAddrAbortHdl
109     b   OsIrqHandler
110     b   _osExceptFiqHdl
111 
112     /* Startup code which will get the machine into supervisor mode */
113     .global reset_vector
114     .type   reset_vector,function
115 reset_vector:
116     /* do some early cpu setup: i/d cache disable, mmu disabled */
117     mrc     p15, 0, r0, c1, c0, 0
118     bic     r0, #(1<<12)
119     bic     r0, #(1<<2 | 1<<0)
120     mcr     p15, 0, r0, c1, c0, 0
121 
122     /* r11: delta of physical address and virtual address */
123     adr     r11, pa_va_offset;此时r11为物理地址  具体原因是硬件决定了第一条指令的地址,当执行到这里pc此时是当前的指令的地址(自然是物理地址)
124                              ;然后而adr伪指令的作用就是得到了当前标识pa_va_offset和当前指令的offset和保存在r11,而代码的实现在这个标识处定
125                              ;义了一个连接地址相关的标识"."所以按照程序连接指定的运行地址(虚拟的)这里保存的值肯定是连接实际的虚拟运行地址所以r0为虚拟地址
126     ldr     r0, [r11]
127     sub     r11, r11, r0     ;进而物理地址减去虚拟地址(连接地址)即就是物理地址和虚拟地址的差。
128 
129     /* if we need to relocate to proper location or not */
130     adr     r4, __exception_handlers            /* r4: base of load address */
131     ldr     r5, =SYS_MEM_BASE                   /* r5: base of physical address */
132     subs    r12, r4, r5                         /* r12: delta of load address and physical address */
133     beq     reloc_img_to_bottom_done            /* if we load image at the bottom of physical address */
134 
135     /* we need to relocate image at the bottom of physical address */
136     ldr     r7, =__exception_handlers           /* r7: base of linked address (or vm address) */
137     ldr     r6, =__bss_start                    /* r6: end of linked address (or vm address) */
138     sub     r6, r7                              /* r6: delta of linked address (or vm address) */
139     add     r6, r4                              /* r6: end of load address */
140 
141 reloc_img_to_bottom_loop:
142     ldr     r7, [r4], #4
143     str     r7, [r5], #4
144     cmp     r4, r6
145     bne     reloc_img_to_bottom_loop
146     sub     pc, r12
147     nop
148     sub     r11, r11, r12                       /* r11: eventual address offset */
149 
150 reloc_img_to_bottom_done:
151     ldr     r4, =g_firstPageTable               /* r4: physical address of translation table and clear it */
152     add     r4, r4, r11
153     bl      page_table_clear
154 
155     PAGE_TABLE_SET SYS_MEM_BASE, KERNEL_VMM_BASE, KERNEL_VMM_SIZE, MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
156     PAGE_TABLE_SET SYS_MEM_BASE, UNCACHED_VMM_BASE, UNCACHED_VMM_SIZE, MMU_INITIAL_MAP_STRONGLY_ORDERED
157     PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_DEVICE_BASE, PERIPH_DEVICE_SIZE, MMU_INITIAL_MAP_DEVICE
158     PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_CACHED_BASE, PERIPH_CACHED_SIZE, MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
159     PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_UNCACHED_BASE, PERIPH_UNCACHED_SIZE, MMU_INITIAL_MAP_STRONGLY_ORDERED
160 
161     orr     r8, r4, #MMU_TTBRx_FLAGS            /* r8 = r4 and set cacheable attributes on translation walk */
162     ldr     r4, =g_mmuJumpPageTable             /* r4: jump pagetable vaddr */
163     add     r4, r4, r11
164     ldr     r4, [r4]
165     add     r4, r4, r11                         /* r4: jump pagetable paddr */
166     bl      page_table_clear
167 
168     /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
169     mov     r6, pc
170     mov     r7, r6                              /* r7: pa (MB aligned)*/
171     lsr     r6, r6, #20                         /* r6: va l1 index */
172     ldr     r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
173     add     r12, r10, r6, lsl #20               /* r12: pa |flags */
174     str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[paIndex] = pt entry */
175     rsb     r7, r11, r6, lsl #20                /* r7: va */
176     str     r12, [r4, r7, lsr #(20 - 2)]        /* jumpTable[vaIndex] = pt entry */
177 
178     bl      mmu_setup                           /* set up the mmu */
179 
180     /* get cpuid and keep it in r11 */
181     mrc     p15, 0, r11, c0, c0, 5
182     and     r11, r11, #MPIDR_CPUID_MASK
183     cmp     r11, #0
184     bne     excstatck_loop_done
185 
186 excstatck_loop:
187     /* clear out the interrupt and exception stack and set magic num to check the overflow */
188     ldr     r0, =__undef_stack
189     ldr     r1, =__exc_stack_top
190     bl      stack_init
191 
192     STACK_MAGIC_SET __undef_stack, #OS_EXC_UNDEF_STACK_SIZE, OS_STACK_MAGIC_WORD
193     STACK_MAGIC_SET __abt_stack, #OS_EXC_ABT_STACK_SIZE, OS_STACK_MAGIC_WORD
194     STACK_MAGIC_SET __irq_stack, #OS_EXC_IRQ_STACK_SIZE, OS_STACK_MAGIC_WORD
195     STACK_MAGIC_SET __fiq_stack, #OS_EXC_FIQ_STACK_SIZE, OS_STACK_MAGIC_WORD
196     STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD
197     STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD
198 
199 excstatck_loop_done:
200 warm_reset:
201     /* initialize interrupt/exception environments */
202     mov    r0, #(CPSR_IRQ_DISABLE |CPSR_FIQ_DISABLE|CPSR_IRQ_MODE)
203     msr    cpsr, r0
204     EXC_SP_SET __irq_stack_top, #OS_EXC_IRQ_STACK_SIZE
205 
206     mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_UNDEF_MODE)
207     msr    cpsr, r0
208     EXC_SP_SET __undef_stack_top, #OS_EXC_UNDEF_STACK_SIZE
209 
210     mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_ABT_MODE)
211     msr    cpsr, r0
212     EXC_SP_SET __abt_stack_top, #OS_EXC_ABT_STACK_SIZE
213 
214     mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_FIQ_MODE)
215     msr    cpsr, r0
216     EXC_SP_SET __fiq_stack_top, #OS_EXC_FIQ_STACK_SIZE
217 
218     /* initialize CPSR (machine state register) */
219     mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE)
220     msr    cpsr, r0
221 
222     /* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
223     msr    spsr, r0
224 
225     /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack */
226     ldr    r0, =__svc_stack_top
227     mov    r2, #OS_EXC_SVC_STACK_SIZE
228     mul    r2, r2, r11
229     sub    r0, r0, r2
230     mov    sp, r0
231 
232     /* enable fpu+neon */
233     MRC    p15, 0, r0, c1, c1, 2
234     ORR    r0, r0, #0xC00
235     BIC    r0, r0, #0xC000
236     MCR    p15, 0, r0, c1, c1, 2
237 
238     LDR    r0, =(0xF << 20)
239     MCR    p15, 0, r0, c1, c0, 2
240 
241     MOV    r3, #0x40000000
242     VMSR   FPEXC, r3
243 
244     LDR    r0, =__exception_handlers
245     MCR    p15, 0, r0, c12, c0, 0
246 
247     cmp    r11, #0
248     bne    cpu_start
249 
250 clear_bss:
251     ldr    r1, =__bss_start
252     ldr    r2, =__bss_end
253     mov    r0, #0
254 
255 bss_loop:
256     cmp    r1, r2
257     strlo  r0, [r1], #4
258     blo    bss_loop
259 
260 #if defined(LOSCFG_CC_STACKPROTECTOR_ALL) || 261     defined(LOSCFG_CC_STACKPROTECTOR_STRONG) || 262     defined(LOSCFG_CC_STACKPROTECTOR)
263     bl     __stack_chk_guard_setup
264 #endif
265 
266 #ifdef LOSCFG_GDB_DEBUG
267     /* GDB_START - generate a compiled_breadk,This function will get GDB stubs started, with a proper environment */
268     bl     GDB_START
269     .word  0xe7ffdeff
270 #endif
271 
272     bl     main
273 
274 _start_hang:
275     b      _start_hang
276 
277 mmu_setup:
278     mov     r12, #0
279     mcr     p15, 0, r12, c8, c7, 0              /* Set c8 to control the TLB and set the mapping to invalid */
280     isb
281 
282     mcr     p15, 0, r12, c2, c0, 2              /* Initialize the c2 register */
283     isb
284 
285     orr     r12, r4, #MMU_TTBRx_FLAGS
286     mcr     p15, 0, r12, c2, c0, 0              /* Set attributes and set temp page table */
287     isb
288 
289     mov     r12, #0x7                           /* 0b0111 */
290     mcr     p15, 0, r12, c3, c0, 0              /* Set DACR with 0b0111, client and manager domian */
291     isb
292 
293     mrc     p15, 0, r12, c1, c0, 0
294     bic     r12, #(1 << 29 | 1 << 28)
295     orr     r12, #(1 << 0)
296     bic     r12, #(1 << 1)
297     orr     r12, #(1 << 2)
298     orr     r12, #(1 << 12)
299     mcr     p15, 0, r12, c1, c0, 0              /* Set SCTLR with r12: Turn on the MMU, I/D cache Disable TRE/AFE */
300     isb
301 
302     ldr     pc,  =1f                            /* Convert to VA */
303 1:
304     mcr     p15, 0, r8, c2, c0, 0               /* Go to the base address saved in C2: Jump to the page table */
305     isb
306 
307     mov     r12, #0
308     mcr     p15, 0, r12, c8, c7, 0
309     isb
310 
311     sub     lr,  r11                            /* adjust lr with delta of physical address and virtual address */
312     bx      lr
313 
314     .code  32
315 
316     .global reset_platform
317     .type   reset_platform,function
318 reset_platform:
319 #ifdef A7SEM_HAL_ROM_MONITOR
320     /* initialize CPSR (machine state register) */
321     mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE)
322     msr    cpsr, r0
323     b      warm_reset
324 #else
325     mov    r0, #0
326     mov    pc, r0   // Jump to reset vector
327 #endif
328 cpu_start:
329     bl     secondary_cpu_start
330     b      .
331 
332 
333 
334 /*
335  * set sp for current cpu
336  * r1 is stack bottom, r0 is stack size, r11 hold cpu id
337  */
338 sp_set:
339     mul    r3, r0, r11
340     sub    r2, r1, r3
341     mov    sp, r2
342     bx     lr          /* set sp */
343 
344 /*
345  * r4: page table base address
346  * r5 and r6 will be used as variable
347  */
348 page_table_clear:
349     mov     r5, #0
350     mov     r6, #0
351 0:
352     str     r5, [r4, r6, lsl #2]
353     add     r6, #1
354     cmp     r6, #0x1000                         /* r6 < 4096 */
355     blt     0b
356     bx      lr
357 
358 /*
359  * r4: page table base address
360  * r6: physical address
361  * r7: virtual address
362  * r8: sizes
363  * r10: flags
364  * r9 and r12 will be used as variable
365  */
366 page_table_build:
367     mov     r9, r6
368     bfc     r9, #20, #12                        /* r9: pa % MB */
369     add     r8, r8, r9
370     add     r8, r8, #(1 << 20)
371     sub     r8, r8, #1
372     lsr     r6, #20                             /* r6 = physical address / MB */
373     lsr     r7, #20                             /* r7 = virtual address / MB */
374     lsr     r8, #20                             /* r8 = roundup(size, MB) */
375 
376 page_table_build_loop:
377     orr     r12, r10, r6, lsl #20               /* r12: flags | physAddr */
378     str     r12, [r4, r7, lsl #2]               /* gPgTable[l1Index] = physAddr | flags */
379     add     r6, #1                              /* physAddr+ */
380     add     r7, #1                              /* l1Index++ */
381     subs    r8, #1                              /* sizes-- */
382     bne     page_table_build_loop
383     bx      lr
384 
385 /*
386  * init stack to initial value
387  * r0 is stack mem start, r1 is stack mem end
388  */
389 stack_init:
390     ldr     r2, =OS_STACK_INIT
391     ldr     r3, =OS_STACK_INIT
392     /* Main loop sets 32 bytes at a time. */
393 stack_init_loop:
394     .irp    offset, #0, #8, #16, #24
395     strd    r2, r3, [r0, offset]
396     .endr
397     add     r0, #32
398     cmp     r0, r1
399     blt     stack_init_loop
400     bx      lr
401 
402 pa_va_offset:
403     .word   .
404 
405 /*
406  * set magic num to stack top for all cpu
407  * r0 is stack top, r1 is stack size, r2 is magic num
408  */
409 excstack_magic:
410     mov     r3, #0
411 excstack_magic_loop:
412     str     r2, [r0]
413     add     r0, r0, r1
414     add     r3, r3, #1
415     cmp     r3, #CORE_NUM
416     blt     excstack_magic_loop
417     bx      lr
418 
419 /*
420  * 0xe51ff004 = "ldr  pc, [pc, #-4]"
421  * next addr value will be the real booting addr
422  */
423 _bootaddr_setup:
424     mov     r0, #0
425     ldr     r1, =0xe51ff004
426     str     r1, [r0]
427 
428     add     r0, r0, #4
429     ldr     r1, =SYS_MEM_BASE
430     str     r1, [r0]
431 
432     dsb
433     isb
434 
435     bx      lr
436 
437 init_done:
438     .long  0xDEADB00B
439 
440     .code  32
441     .data
442 
443 init_flag:
444     .balign 4
445     .long   0
446 
447     /*
448     * Temporary interrupt stack
449     */
450     .section ".int_stack", "wa", %nobits
451     .align  3
452 
453 __undef_stack:
454     .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM
455 __undef_stack_top:
456 
457 __abt_stack:
458     .space OS_EXC_ABT_STACK_SIZE * CORE_NUM
459 __abt_stack_top:
460 
461 __irq_stack:
462     .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM
463 __irq_stack_top:
464 
465 __fiq_stack:
466     .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM
467 __fiq_stack_top:
468 
469 __svc_stack:
470     .space OS_EXC_SVC_STACK_SIZE * CORE_NUM
471 __svc_stack_top:
472 
473 __exc_stack:
474     .space OS_EXC_STACK_SIZE * CORE_NUM
475 __exc_stack_top:

看不太明白就是复位操作其中的计算虚拟地址和物理地址差的部分实现这里单独贴出来。

1 /* r11: delta of physical address and virtual address */
2     adr     r11, pa_va_offset;此时r11为物理地址  具体原因是硬件决定了第一条指令的地址,当执行到这里pc此时是当前的指令的地址(自然是物理地址)
3                              ;然后而adr伪指令的作用就是得到了当前标识pa_va_offset和当前指令的offset和保存在r11,而代码的实现在这个标识处定
4                              ;义了一个连接地址相关的标识"."所以按照程序连接指定的运行地址(虚拟的)这里保存的值肯定是连接实际的虚拟运行地址所以r0为虚拟地址
5     ldr     r0, [r11]
6     sub     r11, r11, r0     ;进而物理地址减去虚拟地址(连接地址)即就是物理地址和虚拟地址的差。

这里主要是对adr这一句的汇编理解的不是很深入,详细了解之后才知道这是一条伪指令在汇编器汇编的时候他会被汇编为sub这个机器码。在程序连接完成后 pa_va_offset 的地址是固定的,并且这一条伪指令的地址也是确定的所以他俩之间的偏移就是确定的,这里记住是后面的符号的地址和 adr r11, pa_va_offset ;这条语句的相对偏移是固定的。所以在汇编这个语句的时候实际上就已经知道他俩的地址差offset,并且这条伪指令在Rn不为pc时最后的结果就如同 sub Rn pc #offset。 所以这里的r11 保存的就是程序的运行时刻的物理地址这一点比较饶,但是从硬件的行为考虑就很容易理解了---硬件启动之后从物理地址的那个地址开始运行具体的硬件模式确定后也是确定的所以这个最后在程序运行到这里的时候PC寄存器就是这一条伪指令的真实的物理地址,所以前面红色字体的内容就能理解了。然后就是后面两句就得到了虚拟地址和物理地址的差。这里也费解但是如果把这句话换一下就好理解多了---实际得到的是链接地址和物理地址的差。具体还要看 pa_va_offset 标识符下的实现下面贴出来:

1 pa_va_offset:
2     .word   .

这简单的一句话其实就是在当前符号连接的地址处放置一个32位的值这个值是程序连接到这里时的连接地址如果熟悉连接脚本就很容易想到了。所以这里在这个符号的地址处存放了这个符号的连接地址,还是有点绕,换个说法就是在一个内存地址(链接地址)上存放了这块内存自己的链接地址,但是在实际存放时具体本存放在那里程序本身自己决定不了,所以这一块内容永远保存的是链接时指定的地址,所以上面代码的实现就得到了程序的链接地址和实际运行物理地址的差,再反过来说就是如果把编译出来的可执行文件放到了链接时指定的地址上的话符号地址里面的内容就是符号的地址。

    之所以注释说是虚拟地址和物理地址的差是因为OS程序在链接时指定的连接地址实际上是在开启了MMU之后的虚拟地址,将来是要拷贝到DRAM中运行的。所以这里的虚拟地址实际上就是链接地址因为必须这样否则系统是无法启动的。最后这里还有一个知识点就是链接地址和运行地址不同为什么还能正常运行呢,这是因为部分代码是PIC的即不关心load地址,也可以通过编译时指定部分代码编译为位置无关的代码,也可查看前面写的博客编译过程中的链接地址和实际运行地址

以上是关于程序如何得到特定符号的所在物理地址的主要内容,如果未能解决你的问题,请参考以下文章

在iOS中,如何找到特定按钮所在代码的位置?

如何通过IP地址得到用户所在的城市名?怎样写代码?

如何告诉/强制 GNU ld 将部分/符号放在输出 ELF 文件的特定部分?

java 项目如何获取项目所在的物理根路径

如何在 Javadoc 中使用 @ 和 符号格式化代码片段?

从 Activity 到特定选项卡/片段的按钮 OnClick