Difference Between ADDR and OFFSET

本文旨在澄清Assembly程序员在使用ADDR和OFFSET指令时的困惑,详细解释了这两种指令在获取变量内存地址时的不同用途,特别是针对局部变量和全局变量的处理方式。

In the initial days when I started writing assembly programs on my own I used to get confused as to when to use ADDR and when to use OFFSET in the program. This article is an attempt to clear the doubts of assembly programmers regarding the meaning and usage of ADDR and OFFSET.

First and foremost, the purpose of using either ADDR or OFFSET is to get the memory address of variables during program execution.

Now, we know that variables in any assembly program are of two types, i.e. local and global variables.

While global variables remain in the memory throughout the execution of the program, local variables exist only during the execution of the functions in which they are declared and will be removed from the stack memory once the function in which they are declared completes is execution.

Since the global variables  exist in memory throughout the lifetime of a program's execution, their memory address is allocated during assembly time by the assembler. The assembler knows the exact location of the global variable's memory address during assembly time.

In case of local variables, the assembler has no idea about the address of the variable as it's address is allocated during runtime in the stack as and when the function in which it is declared is executed.

now coming back to our assembler instructions, OFFSET will get the address of a variable which already has it's address allocated. This in turn means, OFFSET could be used to get the address of global variables only. We cannot receive the address of a local variable by using OFFSET as the address of a local variable is not decided during assembly time.

To overcome this difficulty we have ADDR instruction. This instruction should be used if we want to retrieve the address of a local variable. 

Now naturally a question arises as to how does ADDR know the address of a local variable while OFFSET cannot. Well, even ADDR will not know the actual address of a local variable as it is referred during assembly time. What ADDR actually does is a simple substitution in the code as follows, just before the function is executed.

lea eax, localvar 
push eax

What really this means is that ADDR causes the address of the local variable which is generated during runtime to be returned. lea is used to refer to the stack memory. LEA means Load Effective Address! It is used to load variables from the stack.

If you still did not get it, then imagine a situation as follows.

I am standing somewhere on the street there and you come to meet me there in search of the address of a beautiful girl which you feel I know. So, now your asking me of the address could be considered as the assembly time of the program, you are the assembly program in search of the (girl's) address and I am the assembler.

Now if I know her exact address I'll give it to you: with perfect street address, door number, etc. This is what OFFSET does.

Now if I don't know where she lives, but I know somebody who I know knows the address of that girl, then I'll give you the address of that somebody and ask you to checkout there for the address of the girl you are searching for. That's what ADDR does. So it's clear that even ADDR doesn't have the exact address of the variable.

Now that we clearly know when to use ADDR and OFFSET, another question arises. Can we use ADDR to load global variables????

Yes, of course! If you are referring to global variables using ADDR, then ADDR simply substitutes is as following.

mov eax, 3000h

where 3000h is the actual address of the global variable. Remember, the actual address of a global variable is known during assemble and link time.

But then, why does ADDR use LEA instead of MOV in case of local variables. Well, for the simple reason that 

mov eax,ebp+2

is an invalid CPU instruction. Note that EBP also known as base address is the register used to access stack, and it is in stack where the local variables are stored.

Hence, LEA is used by ADDR in case of local variables.

So it is clear that OFFSET is to be used to global variables and ADDR for local variables. ADDR could ALSO be used while referring to global variables, BUT OFFSET cannot be used while referring to local variables.

Still any doubts? Feel free to mail me to the address on the RHS below. But please do not ask the address of any beautiful girl :-) I won't give it even if I get one ever.

http://www.hitxp.com/comp/pro/asm/120403.htm

xref: /MT6769_15.0.0_release/vnd/vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6768/gpio_init.c HomeAnnotateLine# Scopes# Navigate#Raw Download current directory 1 /* Copyright Statement: 2 * 3 * This software/firmware and related documentation ("MediaTek Software") are 4 * protected under relevant copyright laws. The information contained herein is 5 * confidential and proprietary to MediaTek Inc. and/or its licensors. Without 6 * the prior written permission of MediaTek inc. and/or its licensors, any 7 * reproduction, modification, use or disclosure of MediaTek Software, and 8 * information contained herein, in whole or in part, shall be strictly 9 * prohibited. 10 * 11 * MediaTek Inc. (C) 2017. All rights reserved. 12 * 13 * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES 14 * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE") 15 * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER 16 * ON AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL 17 * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR 19 * NONINFRINGEMENT. NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH 20 * RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, 21 * INCORPORATED IN, OR SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES 22 * TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. 23 * RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO 24 * OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK 25 * SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE 26 * RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR 27 * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S 28 * ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE 29 * RELEASED HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE 30 * MEDIATEK SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE 31 * CHARGE PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE. 32 * 33 * The following software/firmware and/or related documentation ("MediaTek 34 * Software") have been modified by MediaTek Inc. All revisions are subject to 35 * any receiver's applicable license agreements with MediaTek Inc. 36 */ 37 #include <platform/mt_gpio.h> 38 39 #if !defined(FPGA_PLATFORM) && !defined(USE_DTB_NO_DWS) 40 #include <cust_power.h> 41 #include <cust_gpio_boot.h> 42 #endif 43 44 #include <platform/mt_reg_base.h> 45 46 #include <debug.h> 47 #define GPIO_INIT_DEBUG 48 /*----------------------------------------------------------------------------*/ 49 #define GPIOTAG "[GPIO] " 50 #define GPIODBG(fmt, arg...) dprintf(INFO, GPIOTAG "%s: " fmt, __FUNCTION__ ,##arg) 51 #define GPIOERR(fmt, arg...) dprintf(INFO, GPIOTAG "%s: " fmt, __FUNCTION__ ,##arg) 52 #define GPIOVER(fmt, arg...) dprintf(INFO, GPIOTAG "%s: " fmt, __FUNCTION__ ,##arg) 53 54 #define GPIO_WR32(addr, data) DRV_WriteReg32(addr,data) 55 #define GPIO_RD32(addr) DRV_Reg32(addr) 56 57 #define ADDR_BIT 0 58 #define VAL_BIT 1 59 #define MASK_BIT 2 60 61 //#define GPIO_SET_DEFAULT_DBG 62 63 /*----------------------------------------------------------------------------*/ 64 #ifdef FPGA_PLATFORM 65 void mt_gpio_set_default(void) 66 { 67 return; 68 } 69 #else 70 71 #include <platform/gpio_init.h> 72 73 /* #ifdef OPLUS_FEATURE_CHG_BASIC */ 74 /* oplus add for user build disable uart */ 75 #include <platform.h> 76 #include <platform/boot_mode.h> 77 /* #endif */ 78 79 /* #ifdef OPLUS_FEATURE_CHG_BASIC */ 80 /* oplus add for user build disable uart */ 81 #define UART_DT_NODE_RX_NAME "rxpin" 82 #define UART_DT_NODE_TX_NAME "txpin" 83 #define UART_DT_NODE_NAME_SOC "/serial@11002000" 84 /* #endif */ 85 #ifdef USE_DTB_NO_DWS 86 #include <libfdt.h> 87 #include <lk_builtin_dtb.h> 88 extern void msdc_gpio_and_pad_init_by_id(int id); 89 #define ELEMENTS_PER_GPIO 7 90 #define GPIO_DT_NODE_NAME "/gpio@10005000" 91 #define GPIO_DT_NODE_PROP_NAME "gpio_init_default" 92 int mt_gpio_get_default_chip_from_dtb(void) 93 { 94 void *lk_drv_fdt = get_lk_overlayed_dtb(); 95 96 if (lk_drv_fdt == NULL) 97 panic("lk driver fdt is NULL!\n"); 98 int off = fdt_path_offset(lk_drv_fdt, GPIO_DT_NODE_NAME); 99 unsigned int *data; 100 int len = 0, pin, j; 101 102 dprintf(INFO, "[GPIO] Found " GPIO_DT_NODE_NAME "at offset %d\n", off); 103 if (off < 0) { 104 dprintf(CRITICAL, "[GPIO] Failed to find " GPIO_DT_NODE_NAME " in dtb\n"); 105 return -1; 106 } else { 107 data = (unsigned int *)fdt_getprop(lk_drv_fdt, off, GPIO_DT_NODE_PROP_NAME, &len); 108 if (len <= 0 || !data) { 109 dprintf(CRITICAL, "[GPIO] Fail to found property " GPIO_DT_NODE_PROP_NAME "\n"); 110 return -1; 111 } 112 dprintf(INFO, "[GPIO] Found perperty at 0x%08x, len %d\n", (unsigned int)data, len); 113 } 114 115 if ((len > 0) && ((len % ELEMENTS_PER_GPIO) == 0)) { 116 len /= (ELEMENTS_PER_GPIO * 4); //Per element use 4 bytes */ 117 for (j = 0; j < len; j++) { 118 pin = fdt32_to_cpu(*data); 119 120 if (pin < 0 || pin >= MT_GPIO_BASE_MAX) { 121 data += 7; 122 continue; 123 } 124 125 data++; 126 gpio_array[pin].mode = (unsigned char) fdt32_to_cpu(*data); 127 128 data++; 129 gpio_array[pin].dir = (unsigned char) fdt32_to_cpu(*data); 130 131 data++; 132 gpio_array[pin].dataout = (unsigned char) fdt32_to_cpu(*data); 133 134 data++; 135 gpio_array[pin].pullen = (unsigned char) fdt32_to_cpu(*data); 136 137 data++; 138 gpio_array[pin].pull = (unsigned char) fdt32_to_cpu(*data); 139 140 data++; 141 gpio_array[pin].smt = (unsigned char) fdt32_to_cpu(*data); 142 143 data++; 144 } 145 } 146 147 return 0; 148 } 149 #endif 150 151 void mt_gpio_set_default_chip(void) 152 { 153 unsigned pin = 0; 154 155 /* #ifdef OPLUS_FEATURE_CHG_BASIC */ 156 /* oplus add for user build disable uart */ 157 void *lk_drv_fdt = NULL; 158 unsigned int *data_rx; 159 unsigned int *data_tx; 160 int pin_rx = -1; 161 int pin_tx = -1; 162 int off1, len1, len2; 163 /* #endif */ 164 165 #ifdef USE_DTB_NO_DWS 166 /* 167 * Pins set as GPIO_INIT_NO_COVER, 168 * mode setting will not override in 169 * mt_gpio_get_default_chip_from_dtb() 170 */ 171 /* Init pin mode as 0xFF */ 172 for (pin = 0; pin < MT_GPIO_BASE_MAX; pin++) 173 gpio_array[pin].mode = 0xFF; 174 175 if (mt_gpio_get_default_chip_from_dtb() < 0) 176 return; 177 178 /* #ifdef OPLUS_FEATURE_CHG_BASIC */ 179 /* oplus add for user build disable uart */ 180 lk_drv_fdt = get_lk_overlayed_dtb(); 181 182 off1 = fdt_path_offset(lk_drv_fdt, UART_DT_NODE_NAME_SOC); 183 data_rx = (unsigned int *)fdt_getprop(lk_drv_fdt, off1, UART_DT_NODE_RX_NAME, &len1); 184 data_tx = (unsigned int *)fdt_getprop(lk_drv_fdt, off1, UART_DT_NODE_TX_NAME, &len2); 185 if (data_rx != NULL && data_tx != NULL) { 186 pin_rx = fdt32_to_cpu(*data_rx); 187 pin_tx = fdt32_to_cpu(*data_tx); 188 dprintf(CRITICAL, "[GPIO] len1=%d, len2=%d, data_rx=%d, data_tx=%d\n", len1, len2, pin_rx, pin_tx); 189 } 190 /* #endif */ 191 #endif 192 193 for (pin = 0; pin < MT_GPIO_BASE_MAX; pin++) { 194 /* Skip pins which is set as GPIO_INIT_NO_COVER */ 195 if (gpio_array[pin].mode == 0xFF) 196 continue; 197 198 /* set GPIOx_MODE*/ 199 mt_set_gpio_mode(0x80000000 + pin , gpio_array[pin].mode); 200 201 /* set GPIOx_DIR*/ 202 if (gpio_array[pin].dir != 0xFF) 203 mt_set_gpio_dir(0x80000000 + pin, gpio_array[pin].dir); 204 205 /* set GPIOx_PULL*/ 206 if (gpio_array[pin].pullen == 0) 207 gpio_array[pin].pull = GPIO_NO_PULL; 208 if (gpio_array[pin].pull != 0xFF) 209 mt_set_gpio_pull_select(0x80000000 + pin, gpio_array[pin].pull); 210 211 /* set GPIOx_PULLEN*/ 212 if (gpio_array[pin].pullen != 0xFF) 213 mt_set_gpio_pull_enable(0x80000000 + pin , gpio_array[pin].pullen); 214 215 /* set GPIOx_DATAOUT*/ 216 if (gpio_array[pin].dataout != 0xFF) 217 mt_set_gpio_out(0x80000000 + pin, gpio_array[pin].dataout); 218 219 /* set GPIO0_SMT */ 220 if (gpio_array[pin].smt != 0xFF) 221 mt_set_gpio_smt(0x80000000 + pin, gpio_array[pin].smt); 222 223 #ifdef GPIO_SET_DEFAULT_DBG 224 dprintf(CRITICAL, "GPIO%d desired: %d %d %d %d %d %d\n", 225 pin, 226 gpio_array[pin].mode, 227 gpio_array[pin].dir, 228 gpio_array[pin].pull, 229 gpio_array[pin].pullen, 230 gpio_array[pin].dataout, 231 gpio_array[pin].smt); 232 233 /* set GPIOx_MODE*/ 234 dprintf(CRITICAL, "GPIO%d modified: %d %d %d %d %d %d\n", 235 pin, 236 mt_get_gpio_mode(0x80000000 + pin), 237 mt_get_gpio_dir(0x80000000 + pin), 238 mt_get_gpio_pull_select(0x80000000 + pin), 239 mt_get_gpio_pull_enable(0x80000000 + pin), 240 mt_get_gpio_out(0x80000000 + pin), 241 mt_get_gpio_smt(0x80000000 + pin)); 242 #endif 243 244 } 245 /* #ifdef OPLUS_FEATURE_CHG_BASIC */ 246 /* oplus add for user build disable uart */ 247 #ifdef USER_LOAD 248 if (pin_rx == -1 || pin_tx == -1) { 249 dprintf(CRITICAL, "user build, but cannot read rx or tx pin!\n"); 250 } else { 251 if (g_boot_mode == FACTORY_BOOT) { 252 dprintf(CRITICAL, "user build, but FTM mode, do nothing!\n"); 253 } else if (g_boot_arg->log_dynamic_switch == 1) { 254 dprintf(CRITICAL, "user build, but oplus uart, do nothing!\n"); 255 } else { 256 mt_set_gpio_mode(pin_rx, 0); 257 mt_set_gpio_pull_enable(pin_rx, 0); 258 mt_set_gpio_dir(pin_rx, 0); 259 260 mt_set_gpio_mode(pin_tx, 0); 261 mt_set_gpio_pull_enable(pin_tx, 0); 262 mt_set_gpio_dir(pin_tx, 0); 263 264 dprintf(CRITICAL, "user build, oplus set uart0 input & pulldown!\n"); 265 } 266 } 267 #endif 268 /* #endif *//* OPLUS_FEATURE_CHG_BASIC */ 269 } 270 271 void mt_gpio_set_default(void) 272 { 273 mt_gpio_set_default_chip(); 274 275 return; 276 } 277 /*----------------------------------------------------------------------------*/ 278 #if CHECK_POINT_TEST 279 #if defined(GPIO_INIT_DEBUG) 280 static GPIO_REGS saved; 281 #endif 282 283 void mt_gpio_checkpoint_save(void) 284 { 285 #if defined(GPIO_INIT_DEBUG) 286 GPIO_REGS *pReg = (GPIO_REGS*)(GPIO_BASE); 287 GPIO_REGS *cur = &saved; 288 int idx; 289 290 memset(cur, 0x00, sizeof(*cur)); 291 for (idx = 0; idx < sizeof(pReg->dir)/sizeof(pReg->dir[0]); idx++) 292 cur->dir[idx].val = GPIO_RD32(&pReg->dir[idx]); 293 #if 0 294 for (idx = 0; idx < sizeof(pReg->pullen)/sizeof(pReg->pullen[0]); idx++) 295 cur->pullen[idx].val = GPIO_RD32(&pReg->pullen[idx]); 296 for (idx = 0; idx < sizeof(pReg->pullsel)/sizeof(pReg->pullsel[0]); idx++) 297 cur->pullsel[idx].val =GPIO_RD32(&pReg->pullsel[idx]); 298 #endif 299 for (idx = 0; idx < sizeof(pReg->dout)/sizeof(pReg->dout[0]); idx++) 300 cur->dout[idx].val = GPIO_RD32(&pReg->dout[idx]); 301 for (idx = 0; idx < sizeof(pReg->mode)/sizeof(pReg->mode[0]); idx++) 302 cur->mode[idx].val = GPIO_RD32(&pReg->mode[idx]); 303 #endif 304 } 305 /*----------------------------------------------------------------------------*/ 306 EXPORT_SYMBOL(mt_gpio_checkpoint_save); 307 /*----------------------------------------------------------------------------*/ 308 void mt_gpio_dump_diff(GPIO_REGS* pre, GPIO_REGS* cur) 309 { 310 #if defined(GPIO_INIT_DEBUG) 311 GPIO_REGS *pReg = (GPIO_REGS*)(GPIO_BASE); 312 int idx; 313 unsigned char* p = (unsigned char*)pre; 314 unsigned char* q = (unsigned char*)cur; 315 316 GPIOVER("------ dumping difference between %p and %p ------\n", pre, cur); 317 for (idx = 0; idx < sizeof(pReg->dir)/sizeof(pReg->dir[0]); idx++) { 318 if (pre->dir[idx].val != cur->dir[idx].val) 319 GPIOVER("diff: dir[%2d] : 0x%08X <=> 0x%08X\n", idx, pre->dir[idx].val, cur->dir[idx].val); 320 } 321 #if 0 322 for (idx = 0; idx < sizeof(pReg->pullen)/sizeof(pReg->pullen[0]); idx++) { 323 if (pre->pullen[idx].val != cur->pullen[idx].val) 324 GPIOVER("diff: pullen[%2d] : 0x%08X <=> 0x%08X\n", idx, pre->pullen[idx].val, cur->pullen[idx].val); 325 } 326 for (idx = 0; idx < sizeof(pReg->pullsel)/sizeof(pReg->pullsel[0]); idx++) { 327 if (pre->pullsel[idx].val != cur->pullsel[idx].val) 328 GPIOVER("diff: pullsel[%2d]: 0x%08X <=> 0x%08X\n", idx, pre->pullsel[idx].val, cur->pullsel[idx].val); 329 } 330 #endif 331 for (idx = 0; idx < sizeof(pReg->dout)/sizeof(pReg->dout[0]); idx++) { 332 if (pre->dout[idx].val != cur->dout[idx].val) 333 GPIOVER("diff: dout[%2d] : 0x%08X <=> 0x%08X\n", idx, pre->dout[idx].val, cur->dout[idx].val); 334 } 335 for (idx = 0; idx < sizeof(pReg->mode)/sizeof(pReg->mode[0]); idx++) { 336 if (pre->mode[idx].val != cur->mode[idx].val) 337 GPIOVER("diff: mode[%2d] : 0x%08X <=> 0x%08X\n", idx, pre->mode[idx].val, cur->mode[idx].val); 338 } 339 340 for (idx = 0; idx < sizeof(*pre); idx++) { 341 if (p[idx] != q[idx]) 342 GPIOVER("diff: raw[%2d]: 0x%02X <=> 0x%02X\n", idx, p[idx], q[idx]); 343 } 344 GPIOVER("memcmp(%p, %p, %d) = %d\n", p, q, sizeof(*pre), memcmp(p, q, sizeof(*pre))); 345 GPIOVER("------ dumping difference end --------------------------------\n"); 346 #endif 347 } 348 /*----------------------------------------------------------------------------*/ 349 void mt_gpio_checkpoint_compare(void) 350 { 351 #if defined(GPIO_INIT_DEBUG) 352 GPIO_REGS *pReg = (GPIO_REGS*)(GPIO_BASE); 353 GPIO_REGS latest; 354 GPIO_REGS *cur = &latest; 355 int idx; 356 357 memset(cur, 0x00, sizeof(*cur)); 358 for (idx = 0; idx < sizeof(pReg->dir)/sizeof(pReg->dir[0]); idx++) 359 cur->dir[idx].val = GPIO_RD32(&pReg->dir[idx]); 360 #if 0 361 for (idx = 0; idx < sizeof(pReg->pullen)/sizeof(pReg->pullen[0]); idx++) 362 cur->pullen[idx].val = GPIO_RD32(&pReg->pullen[idx]); 363 for (idx = 0; idx < sizeof(pReg->pullsel)/sizeof(pReg->pullsel[0]); idx++) 364 cur->pullsel[idx].val =GPIO_RD32(&pReg->pullsel[idx]); 365 #endif 366 for (idx = 0; idx < sizeof(pReg->dout)/sizeof(pReg->dout[0]); idx++) 367 cur->dout[idx].val = GPIO_RD32(&pReg->dout[idx]); 368 for (idx = 0; idx < sizeof(pReg->mode)/sizeof(pReg->mode[0]); idx++) 369 cur->mode[idx].val = GPIO_RD32(&pReg->mode[idx]); 370 371 //mt_gpio_dump_diff(&latest, &saved); 372 //GPIODBG("memcmp(%p, %p, %d) = %d\n", &latest, &saved, sizeof(GPIO_REGS), memcmp(&latest, &saved, sizeof(GPIO_REGS))); 373 if (memcmp(&latest, &saved, sizeof(GPIO_REGS))) { 374 GPIODBG("checkpoint compare fail!!\n"); 375 GPIODBG("dump checkpoint....\n"); 376 //mt_gpio_dump(&saved); 377 GPIODBG("\n\n"); 378 GPIODBG("dump current state\n"); 379 //mt_gpio_dump(&latest); 380 GPIODBG("\n\n"); 381 mt_gpio_dump_diff(&saved, &latest); 382 //WARN_ON(1); 383 } else { 384 GPIODBG("checkpoint compare success!!\n"); 385 } 386 #endif 387 } 388 /*----------------------------------------------------------------------------*/ 389 EXPORT_SYMBOL(mt_gpio_checkpoint_compare); 390 /*----------------------------------------------------------------------------*/ 391 #endif 392 #endif 393 served by {OpenGrok Last Index Update: Thu Aug 07 23:03:58 CST 2025mt_gpio_get_default_chip_from_dtb 获取的g_boot_mode =0 ,但是预期是4
11-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值