1 /*
2 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
3 * Copyright (c) 2020-2021 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 /**
33 * @defgroup los_vm_dump virtual memory dump operation
34 * @ingroup kernel
35 */
36
37 #include "los_vm_dump.h"
38 #include "los_mmu_descriptor_v6.h"
39 #ifdef LOSCFG_FS_VFS
40 #include "fs/file.h"
41 #include "vnode.h"
42 #endif
43 #include "los_printf.h"
44 #include "los_vm_page.h"
45 #include "los_vm_phys.h"
46 #include "los_process_pri.h"
47 #include "los_atomic.h"
48 #include "los_vm_lock.h"
49 #include "los_memory_pri.h"
50
51
52 #ifdef LOSCFG_KERNEL_VM
53
54 #define FLAG_SIZE 4
55 #define FLAG_START 2
56
OsGetRegionNameOrFilePath(LosVmMapRegion * region)57 const CHAR *OsGetRegionNameOrFilePath(LosVmMapRegion *region)
58 {
59 struct Vnode *vnode = NULL;
60 if (region == NULL) {
61 return "";
62 #ifdef LOSCFG_FS_VFS
63 } else if (LOS_IsRegionFileValid(region)) {
64 vnode = region->unTypeData.rf.vnode;
65 return vnode->filePath;
66 #endif
67 } else if (region->regionFlags & VM_MAP_REGION_FLAG_HEAP) {
68 return "HEAP";
69 } else if (region->regionFlags & VM_MAP_REGION_FLAG_STACK) {
70 return "STACK";
71 } else if (region->regionFlags & VM_MAP_REGION_FLAG_TEXT) {
72 return "Text";
73 } else if (region->regionFlags & VM_MAP_REGION_FLAG_VDSO) {
74 return "VDSO";
75 } else if (region->regionFlags & VM_MAP_REGION_FLAG_MMAP) {
76 return "MMAP";
77 } else if (region->regionFlags & VM_MAP_REGION_FLAG_SHM) {
78 return "SHM";
79 } else {
80 return "";
81 }
82 return "";
83 }
84
OsRegionOverlapCheckUnlock(LosVmSpace * space,LosVmMapRegion * region)85 INT32 OsRegionOverlapCheckUnlock(LosVmSpace *space, LosVmMapRegion *region)
86 {
87 LosVmMapRegion *regionTemp = NULL;
88 LosRbNode *pstRbNode = NULL;
89 LosRbNode *pstRbNodeNext = NULL;
90
91 /* search the region list */
92 RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
93 regionTemp = (LosVmMapRegion *)pstRbNode;
94 if (region->range.base == regionTemp->range.base && region->range.size == regionTemp->range.size) {
95 continue;
96 }
97 if (((region->range.base + region->range.size) > regionTemp->range.base) &&
98 (region->range.base < (regionTemp->range.base + regionTemp->range.size))) {
99 VM_ERR("overlap between regions:\n"
100 "flags:%#x base:%p size:%08x space:%p\n"
101 "flags:%#x base:%p size:%08x space:%p",
102 region->regionFlags, region->range.base, region->range.size, region->space,
103 regionTemp->regionFlags, regionTemp->range.base, regionTemp->range.size, regionTemp->space);
104 return -1;
105 }
106 RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
107
108 return 0;
109 }
110
OsShellCmdProcessVmUsage(LosVmSpace * space)111 UINT32 OsShellCmdProcessVmUsage(LosVmSpace *space)
112 {
113 LosVmMapRegion *region = NULL;
114 LosRbNode *pstRbNode = NULL;
115 LosRbNode *pstRbNodeNext = NULL;
116 UINT32 used = 0;
117
118 if (space == NULL) {
119 return 0;
120 }
121
122 if (space == LOS_GetKVmSpace()) {
123 OsShellCmdProcessPmUsage(space, NULL, &used);
124 } else {
125 RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
126 region = (LosVmMapRegion *)pstRbNode;
127 used += region->range.size;
128 RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
129 }
130
131 return used;
132 }
133
OsKProcessPmUsage(LosVmSpace * kSpace,UINT32 * actualPm)134 VOID OsKProcessPmUsage(LosVmSpace *kSpace, UINT32 *actualPm)
135 {
136 UINT32 memUsed;
137 UINT32 totalMem;
138 UINT32 freeMem;
139 UINT32 usedCount = 0;
140 UINT32 totalCount = 0;
141 LosVmSpace *space = NULL;
142 LOS_DL_LIST *spaceList = NULL;
143 UINT32 UProcessUsed = 0;
144 UINT32 pmTmp;
145
146 if (actualPm == NULL) {
147 return;
148 }
149
150 memUsed = LOS_MemTotalUsedGet(m_aucSysMem1);
151 totalMem = LOS_MemPoolSizeGet(m_aucSysMem1);
152 freeMem = totalMem - memUsed;
153
154 OsVmPhysUsedInfoGet(&usedCount, &totalCount);
155 /* Kernel resident memory, include default heap memory */
156 memUsed = SYS_MEM_SIZE_DEFAULT - (totalCount << PAGE_SHIFT);
157
158 spaceList = LOS_GetVmSpaceList();
159 LOS_DL_LIST_FOR_EACH_ENTRY(space, spaceList, LosVmSpace, node) {
160 if (space == LOS_GetKVmSpace()) {
161 continue;
162 }
163 OsUProcessPmUsage(space, NULL, &pmTmp);
164 UProcessUsed += pmTmp;
165 }
166
167 /* Kernel dynamic memory, include extended heap memory */
168 memUsed += ((usedCount << PAGE_SHIFT) - UProcessUsed);
169 /* Remaining heap memory */
170 memUsed -= freeMem;
171
172 *actualPm = memUsed;
173 }
174
OsShellCmdProcessPmUsage(LosVmSpace * space,UINT32 * sharePm,UINT32 * actualPm)175 VOID OsShellCmdProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm)
176 {
177 if (space == NULL) {
178 return;
179 }
180
181 if ((sharePm == NULL) && (actualPm == NULL)) {
182 return;
183 }
184
185 if (space == LOS_GetKVmSpace()) {
186 OsKProcessPmUsage(space, actualPm);
187 } else {
188 OsUProcessPmUsage(space, sharePm, actualPm);
189 }
190 }
191
OsUProcessPmUsage(LosVmSpace * space,UINT32 * sharePm,UINT32 * actualPm)192 VOID OsUProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm)
193 {
194 LosVmMapRegion *region = NULL;
195 LosRbNode *pstRbNode = NULL;
196 LosRbNode *pstRbNodeNext = NULL;
197 LosVmPage *page = NULL;
198 VADDR_T vaddr;
199 size_t size;
200 PADDR_T paddr;
201 STATUS_T ret;
202 INT32 shareRef;
203
204 if (sharePm != NULL) {
205 *sharePm = 0;
206 }
207
208 if (actualPm != NULL) {
209 *actualPm = 0;
210 }
211
212 RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
213 region = (LosVmMapRegion *)pstRbNode;
214 vaddr = region->range.base;
215 size = region->range.size;
216 for (; size > 0; vaddr += PAGE_SIZE, size -= PAGE_SIZE) {
217 ret = LOS_ArchMmuQuery(&space->archMmu, vaddr, &paddr, NULL);
218 if (ret < 0) {
219 continue;
220 }
221 page = LOS_VmPageGet(paddr);
222 if (page == NULL) {
223 continue;
224 }
225
226 shareRef = LOS_AtomicRead(&page->refCounts);
227 if (shareRef > 1) {
228 if (sharePm != NULL) {
229 *sharePm += PAGE_SIZE;
230 }
231 if (actualPm != NULL) {
232 *actualPm += PAGE_SIZE / shareRef;
233 }
234 } else {
235 if (actualPm != NULL) {
236 *actualPm += PAGE_SIZE;
237 }
238 }
239 }
240 RB_SCAN_SAFE_END(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)
241 }
242
OsGetPIDByAspace(LosVmSpace * space)243 LosProcessCB *OsGetPIDByAspace(LosVmSpace *space)
244 {
245 UINT32 pid;
246 UINT32 intSave;
247 LosProcessCB *processCB = NULL;
248
249 SCHEDULER_LOCK(intSave);
250 for (pid = 0; pid < g_processMaxNum; ++pid) {
251 processCB = g_processCBArray + pid;
252 if (OsProcessIsUnused(processCB)) {
253 continue;
254 }
255
256 if (processCB->vmSpace == space) {
257 SCHEDULER_UNLOCK(intSave);
258 return processCB;
259 }
260 }
261 SCHEDULER_UNLOCK(intSave);
262 return NULL;
263 }
264
OsCountRegionPages(LosVmSpace * space,LosVmMapRegion * region,UINT32 * pssPages)265 UINT32 OsCountRegionPages(LosVmSpace *space, LosVmMapRegion *region, UINT32 *pssPages)
266 {
267 UINT32 regionPages = 0;
268 PADDR_T paddr;
269 VADDR_T vaddr;
270 UINT32 ref;
271 STATUS_T status;
272 float pss = 0;
273 LosVmPage *page = NULL;
274
275 for (vaddr = region->range.base; vaddr < region->range.base + region->range.size; vaddr = vaddr + PAGE_SIZE) {
276 status = LOS_ArchMmuQuery(&space->archMmu, vaddr, &paddr, NULL);
277 if (status == LOS_OK) {
278 regionPages++;
279 if (pssPages == NULL) {
280 continue;
281 }
282 page = LOS_VmPageGet(paddr);
283 if (page != NULL) {
284 ref = LOS_AtomicRead(&page->refCounts);
285 pss += ((ref > 0) ? (1.0 / ref) : 1);
286 } else {
287 pss += 1;
288 }
289 }
290 }
291
292 if (pssPages != NULL) {
293 *pssPages = (UINT32)(pss + 0.5);
294 }
295
296 return regionPages;
297 }
298
OsCountAspacePages(LosVmSpace * space)299 UINT32 OsCountAspacePages(LosVmSpace *space)
300 {
301 UINT32 spacePages = 0;
302 LosVmMapRegion *region = NULL;
303 LosRbNode *pstRbNode = NULL;
304 LosRbNode *pstRbNodeNext = NULL;
305
306 RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
307 region = (LosVmMapRegion *)pstRbNode;
308 spacePages += OsCountRegionPages(space, region, NULL);
309 RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
310 return spacePages;
311 }
312
OsArchFlagsToStr(const UINT32 archFlags)313 CHAR *OsArchFlagsToStr(const UINT32 archFlags)
314 {
315 UINT32 index;
316 UINT32 cacheFlags = archFlags & VM_MAP_REGION_FLAG_CACHE_MASK;
317 UINT32 flagSize = FLAG_SIZE * BITMAP_BITS_PER_WORD * sizeof(CHAR);
318 CHAR *archMmuFlagsStr = (CHAR *)LOS_MemAlloc(m_aucSysMem0, flagSize);
319 if (archMmuFlagsStr == NULL) {
320 return NULL;
321 }
322 (VOID)memset_s(archMmuFlagsStr, flagSize, 0, flagSize);
323 switch (cacheFlags) {
324 case 0UL:
325 (VOID)strcat_s(archMmuFlagsStr, flagSize, " CH\0");
326 break;
327 case 1UL:
328 (VOID)strcat_s(archMmuFlagsStr, flagSize, " UC\0");
329 break;
330 case 2UL:
331 (VOID)strcat_s(archMmuFlagsStr, flagSize, " UD\0");
332 break;
333 case 3UL:
334 (VOID)strcat_s(archMmuFlagsStr, flagSize, " WC\0");
335 break;
336 default:
337 break;
338 }
339
340 static const CHAR FLAGS[BITMAP_BITS_PER_WORD][FLAG_SIZE] = {
341 [0 ... (__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_USER) - 2)] = "???\0",
342 [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_USER) - 1] = " US\0",
343 [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_READ) - 1] = " RD\0",
344 [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_WRITE) - 1] = " WR\0",
345 [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_EXECUTE) - 1] = " EX\0",
346 [__builtin_ffsl(VM_MAP_REGION_FLAG_NS) - 1] = " NS\0",
347 [__builtin_ffsl(VM_MAP_REGION_FLAG_INVALID) - 1] = " IN\0",
348 [__builtin_ffsl(VM_MAP_REGION_FLAG_INVALID) ... (BITMAP_BITS_PER_WORD - 1)] = "???\0",
349 };
350
351 for (index = FLAG_START; index < BITMAP_BITS_PER_WORD; index++) {
352 if (FLAGS[index][0] == '?') {
353 continue;
354 }
355
356 if (archFlags & (1UL << index)) {
357 UINT32 status = strcat_s(archMmuFlagsStr, flagSize, FLAGS[index]);
358 if (status != 0) {
359 PRINTK("error\n");
360 }
361 }
362 }
363
364 return archMmuFlagsStr;
365 }
366
OsDumpRegion2(LosVmSpace * space,LosVmMapRegion * region)367 VOID OsDumpRegion2(LosVmSpace *space, LosVmMapRegion *region)
368 {
369 UINT32 pssPages = 0;
370 UINT32 regionPages;
371
372 regionPages = OsCountRegionPages(space, region, &pssPages);
373 CHAR *flagsStr = OsArchFlagsToStr(region->regionFlags);
374 if (flagsStr == NULL) {
375 return;
376 }
377 PRINTK("\t %#010x %-32.32s %#010x %#010x %-15.15s %4d %4d\n",
378 region, OsGetRegionNameOrFilePath(region), region->range.base,
379 region->range.size, flagsStr, regionPages, pssPages);
380 (VOID)LOS_MemFree(m_aucSysMem0, flagsStr);
381 }
382
OsDumpAspace(LosVmSpace * space)383 VOID OsDumpAspace(LosVmSpace *space)
384 {
385 LosVmMapRegion *region = NULL;
386 LosRbNode *pstRbNode = NULL;
387 LosRbNode *pstRbNodeNext = NULL;
388 UINT32 spacePages;
389 LosProcessCB *pcb = OsGetPIDByAspace(space);
390
391 if (pcb == NULL) {
392 return;
393 }
394
395 spacePages = OsCountAspacePages(space);
396 PRINTK("\r\n PID aspace name base size pages \n");
397 PRINTK(" ---- ------ ---- ---- ----- ----\n");
398 PRINTK(" %-4d %#010x %-10.10s %#010x %#010x %d\n", pcb->processID, space, pcb->processName,
399 space->base, space->size, spacePages);
400 PRINTK("\r\n\t region name base size mmu_flags pages pg/ref\n");
401 PRINTK("\t ------ ---- ---- ---- --------- ----- -----\n");
402 RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
403 region = (LosVmMapRegion *)pstRbNode;
404 if (region != NULL) {
405 OsDumpRegion2(space, region);
406 (VOID)OsRegionOverlapCheck(space, region);
407 } else {
408 PRINTK("region is NULL\n");
409 }
410 RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
411 return;
412 }
413
OsDumpAllAspace(VOID)414 VOID OsDumpAllAspace(VOID)
415 {
416 LosVmSpace *space = NULL;
417 LOS_DL_LIST *aspaceList = LOS_GetVmSpaceList();
418 LOS_DL_LIST_FOR_EACH_ENTRY(space, aspaceList, LosVmSpace, node) {
419 (VOID)LOS_MuxAcquire(&space->regionMux);
420 OsDumpAspace(space);
421 (VOID)LOS_MuxRelease(&space->regionMux);
422 }
423 return;
424 }
425
OsRegionOverlapCheck(LosVmSpace * space,LosVmMapRegion * region)426 STATUS_T OsRegionOverlapCheck(LosVmSpace *space, LosVmMapRegion *region)
427 {
428 int ret;
429
430 if (space == NULL || region == NULL) {
431 return -1;
432 }
433
434 (VOID)LOS_MuxAcquire(&space->regionMux);
435 ret = OsRegionOverlapCheckUnlock(space, region);
436 (VOID)LOS_MuxRelease(&space->regionMux);
437 return ret;
438 }
439
OsDumpPte(VADDR_T vaddr)440 VOID OsDumpPte(VADDR_T vaddr)
441 {
442 UINT32 l1Index = vaddr >> MMU_DESCRIPTOR_L1_SMALL_SHIFT;
443 LosVmSpace *space = LOS_SpaceGet(vaddr);
444 UINT32 ttEntry;
445 LosVmPage *page = NULL;
446 PTE_T *l2Table = NULL;
447 UINT32 l2Index;
448
449 if (space == NULL) {
450 return;
451 }
452
453 ttEntry = space->archMmu.virtTtb[l1Index];
454 if (ttEntry) {
455 l2Table = LOS_PaddrToKVaddr(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry));
456 l2Index = (vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> PAGE_SHIFT;
457 if (l2Table == NULL) {
458 goto ERR;
459 }
460 page = LOS_VmPageGet(l2Table[l2Index] & ~(PAGE_SIZE - 1));
461 if (page == NULL) {
462 goto ERR;
463 }
464 PRINTK("vaddr %p, l1Index %d, ttEntry %p, l2Table %p, l2Index %d, pfn %p count %d\n",
465 vaddr, l1Index, ttEntry, l2Table, l2Index, l2Table[l2Index], LOS_AtomicRead(&page->refCounts));
466 } else {
467 PRINTK("vaddr %p, l1Index %d, ttEntry %p\n", vaddr, l1Index, ttEntry);
468 }
469 return;
470 ERR:
471 PRINTK("%s, error vaddr: %#x, l2Table: %#x, l2Index: %#x\n", __FUNCTION__, vaddr, l2Table, l2Index);
472 }
473
OsVmPhySegPagesGet(LosVmPhysSeg * seg)474 UINT32 OsVmPhySegPagesGet(LosVmPhysSeg *seg)
475 {
476 UINT32 intSave;
477 UINT32 flindex;
478 UINT32 segFreePages = 0;
479
480 LOS_SpinLockSave(&seg->freeListLock, &intSave);
481 for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
482 segFreePages += ((1 << flindex) * seg->freeList[flindex].listCnt);
483 }
484 LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
485
486 return segFreePages;
487 }
488
OsVmPhysDump(VOID)489 VOID OsVmPhysDump(VOID)
490 {
491 LosVmPhysSeg *seg = NULL;
492 UINT32 segFreePages;
493 UINT32 totalFreePages = 0;
494 UINT32 totalPages = 0;
495 UINT32 segIndex;
496 UINT32 intSave;
497 UINT32 flindex;
498 UINT32 listCount[VM_LIST_ORDER_MAX] = {0};
499
500 for (segIndex = 0; segIndex < g_vmPhysSegNum; segIndex++) {
501 seg = &g_vmPhysSeg[segIndex];
502 if (seg->size > 0) {
503 segFreePages = OsVmPhySegPagesGet(seg);
504 #ifdef LOSCFG_SHELL_CMD_DEBUG
505 PRINTK("\r\n phys_seg base size free_pages \n");
506 PRINTK(" -------- ------- ---------- --------- \n");
507 #endif
508 PRINTK(" 0x%08x 0x%08x 0x%08x %8u \n", seg, seg->start, seg->size, segFreePages);
509 totalFreePages += segFreePages;
510 totalPages += (seg->size >> PAGE_SHIFT);
511
512 LOS_SpinLockSave(&seg->freeListLock, &intSave);
513 for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
514 listCount[flindex] = seg->freeList[flindex].listCnt;
515 }
516 LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
517 for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
518 PRINTK("order = %d, free_count = %d\n", flindex, listCount[flindex]);
519 }
520
521 PRINTK("active anon %d\n", seg->lruSize[VM_LRU_ACTIVE_ANON]);
522 PRINTK("inactive anon %d\n", seg->lruSize[VM_LRU_INACTIVE_ANON]);
523 PRINTK("active file %d\n", seg->lruSize[VM_LRU_ACTIVE_FILE]);
524 PRINTK("inactive file %d\n", seg->lruSize[VM_LRU_INACTIVE_FILE]);
525 }
526 }
527 PRINTK("\n\rpmm pages: total = %u, used = %u, free = %u\n",
528 totalPages, (totalPages - totalFreePages), totalFreePages);
529 }
530
OsVmPhysUsedInfoGet(UINT32 * usedCount,UINT32 * totalCount)531 VOID OsVmPhysUsedInfoGet(UINT32 *usedCount, UINT32 *totalCount)
532 {
533 UINT32 index;
534 UINT32 segFreePages;
535 LosVmPhysSeg *physSeg = NULL;
536
537 if (usedCount == NULL || totalCount == NULL) {
538 return;
539 }
540 *usedCount = 0;
541 *totalCount = 0;
542
543 for (index = 0; index < g_vmPhysSegNum; index++) {
544 physSeg = &g_vmPhysSeg[index];
545 if (physSeg->size > 0) {
546 *totalCount += physSeg->size >> PAGE_SHIFT;
547 segFreePages = OsVmPhySegPagesGet(physSeg);
548 *usedCount += (*totalCount - segFreePages);
549 }
550 }
551 }
552 #endif
553
554