sgx_gdb.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */
  2. /* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */
  3. #include <stddef.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <fcntl.h>
  7. #include <errno.h>
  8. #include <stdarg.h>
  9. #include <sys/syscall.h>
  10. #include <sys/ptrace.h>
  11. #include <sys/user.h>
  12. #include <assert.h>
  13. #include <wait.h>
  14. #include <signal.h>
  15. #include "sgx_gdb.h"
  16. #include "../sgx_arch.h"
  17. //#define DEBUG_GDB_PTRACE 1
  18. #if DEBUG_GDB_PTRACE == 1
  19. #define DEBUG(fmt, ...) do { fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
  20. #else
  21. #define DEBUG(fmt, ...) do {} while (0)
  22. #endif
  23. static
  24. long int host_ptrace (enum __ptrace_request request, pid_t pid,
  25. void * addr, void * data)
  26. {
  27. long int res, ret;
  28. if (request > 0 && request < 4)
  29. data = &res;
  30. ret = syscall((long int) SYS_ptrace,
  31. (long int) request,
  32. (long int) pid,
  33. (long int) addr,
  34. (long int) data);
  35. if (ret < 0) {
  36. if (request >= 0x4000)
  37. DEBUG("ptrace(0x%x, %d, %p, %p) = err %d\n", request, pid, addr,
  38. data, errno);
  39. else
  40. DEBUG("ptrace(%d, %d, %p, %p) = err %d\n", request, pid, addr,
  41. data, errno);
  42. } else {
  43. if (request >= 0x4000)
  44. DEBUG("ptrace(0x%x, %d, %p, %p) = 0x%lx\n", request, pid, addr,
  45. data, ret);
  46. else
  47. DEBUG("ptrace(%d, %d, %p, %p) = 0x%lx\n", request, pid, addr,
  48. data, ret);
  49. }
  50. if (ret >= 0 && request > 0 && request < 4)
  51. ret = res;
  52. return ret;
  53. }
  54. static inline
  55. int host_peektids (int memdev, struct enclave_dbginfo * ei)
  56. {
  57. long int res;
  58. void * addr = (void *) DBGINFO_ADDR +
  59. offsetof(struct enclave_dbginfo, thread_tids);
  60. void * data = (void *) ei +
  61. offsetof(struct enclave_dbginfo, thread_tids);
  62. errno = 0;
  63. for (int off = 0 ; off < sizeof(ei->thread_tids) ;
  64. off += sizeof(long int)) {
  65. res = host_ptrace(PTRACE_PEEKDATA, ei->pid, addr + off, NULL);
  66. if (errno) {
  67. DEBUG("Failed getting thread information\n");
  68. return -1;
  69. }
  70. *(long int *) (data + off) = res;
  71. }
  72. return 0;
  73. }
  74. static inline
  75. int host_peekonetid (pid_t pid, int memdev, struct enclave_dbginfo * ei)
  76. {
  77. int ret = host_peektids(memdev, ei);
  78. if (ret < 0)
  79. return ret;
  80. for (int i = 0 ; i < MAX_DBG_THREADS ; i++)
  81. if (ei->thread_tids[i] == pid)
  82. return 0;
  83. DEBUG("No such thread: %d\n", pid);
  84. return -ESRCH;
  85. }
  86. static
  87. void * get_gpr_addr (int memdev, pid_t pid, struct enclave_dbginfo * ei)
  88. {
  89. void * tcs_addr = NULL;
  90. struct { uint64_t ossa; uint32_t cssa, nssa; } tcs_part;
  91. int ret;
  92. for (int i = 0 ; i < MAX_DBG_THREADS ; i++)
  93. if (ei->thread_tids[i] == pid) {
  94. tcs_addr = ei->tcs_addrs[i];
  95. break;
  96. }
  97. if (!tcs_addr) {
  98. DEBUG("No such thread: %d\n", pid);
  99. errno = -ESRCH;
  100. return NULL;
  101. }
  102. ret = pread(memdev, &tcs_part, sizeof(tcs_part),
  103. (off_t) tcs_addr + offsetof(sgx_arch_tcs_t, ossa));
  104. if (ret < sizeof(tcs_part)) {
  105. DEBUG("Can't read TCS data (%p)\n", tcs_addr);
  106. if (ret >= 0)
  107. errno = -EFAULT;
  108. return NULL;
  109. }
  110. DEBUG("%d: TCS at 0x%lx\n", pid, (uint64_t) tcs_addr);
  111. DEBUG("%d: TCS.ossa = 0x%lx TCS.cssa = %d\n", pid, tcs_part.ossa, tcs_part.cssa);
  112. assert(tcs_part.cssa > 0);
  113. return (void *) ei->base + tcs_part.ossa + ei->ssaframesize * tcs_part.cssa
  114. - sizeof(sgx_arch_gpr_t);
  115. }
  116. static
  117. int host_peekgpr (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  118. sgx_arch_gpr_t * gpr)
  119. {
  120. void * gpr_addr = get_gpr_addr(memdev, pid, ei);
  121. int ret;
  122. if (!gpr_addr)
  123. return -1;
  124. ret = pread(memdev, gpr, sizeof(sgx_arch_gpr_t), (off_t) gpr_addr);
  125. if (ret < sizeof(sgx_arch_gpr_t)) {
  126. DEBUG("Can't read GPR data (%p)\n", gpr_addr);
  127. if (ret >= 0) {
  128. errno = -EFAULT;
  129. ret = -1;
  130. }
  131. return ret;
  132. }
  133. DEBUG("%d: peek GPR RIP 0x%08lx RBP 0x%08lx\n", pid, gpr->rip, gpr->rbp);
  134. return 0;
  135. }
  136. static
  137. int host_pokegpr (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  138. const sgx_arch_gpr_t * gpr)
  139. {
  140. void * gpr_addr = get_gpr_addr(memdev, pid, ei);
  141. int ret;
  142. if (!gpr_addr)
  143. return -1;
  144. DEBUG("%d: poke GPR RIP 0x%08lx RBP 0x%08lx\n", pid, gpr->rip, gpr->rbp);
  145. assert(gpr->rip > ei->base && gpr->rip < ei->base + ei->size);
  146. assert(gpr->rsp > ei->base && gpr->rsp < ei->base + ei->size);
  147. ret = pwrite(memdev, gpr, sizeof(sgx_arch_gpr_t), (off_t) gpr_addr);
  148. if (ret < sizeof(sgx_arch_gpr_t)) {
  149. DEBUG("Can't write GPR data (%p)\n", (void *) gpr_addr);
  150. if (ret >= 0) {
  151. errno = -EFAULT;
  152. ret = -1;
  153. }
  154. return ret;
  155. }
  156. return 0;
  157. }
  158. static inline
  159. void fill_regs (struct user_regs_struct * regs, const sgx_arch_gpr_t * gpr)
  160. {
  161. regs->r15 = gpr->r15;
  162. regs->r14 = gpr->r14;
  163. regs->r13 = gpr->r13;
  164. regs->r12 = gpr->r12;
  165. regs->rbp = gpr->rbp;
  166. regs->rbx = gpr->rbx;
  167. regs->r11 = gpr->r11;
  168. regs->r10 = gpr->r10;
  169. regs->r9 = gpr->r9;
  170. regs->r8 = gpr->r8;
  171. regs->rax = gpr->rax;
  172. regs->rcx = gpr->rcx;
  173. regs->rdx = gpr->rdx;
  174. regs->rsi = gpr->rsi;
  175. regs->rdi = gpr->rdi;
  176. regs->orig_rax = gpr->rax;
  177. regs->rip = gpr->rip;
  178. regs->eflags = gpr->rflags;
  179. regs->rsp = gpr->rsp;
  180. }
  181. static inline
  182. void fill_gpr (sgx_arch_gpr_t * gpr, const struct user_regs_struct * regs)
  183. {
  184. gpr->r15 = regs->r15;
  185. gpr->r14 = regs->r14;
  186. gpr->r13 = regs->r13;
  187. gpr->r12 = regs->r12;
  188. gpr->rbp = regs->rbp;
  189. gpr->rbx = regs->rbx;
  190. gpr->r11 = regs->r11;
  191. gpr->r10 = regs->r10;
  192. gpr->r9 = regs->r9;
  193. gpr->r8 = regs->r8;
  194. gpr->rax = regs->rax;
  195. gpr->rcx = regs->rcx;
  196. gpr->rdx = regs->rdx;
  197. gpr->rsi = regs->rsi;
  198. gpr->rdi = regs->rdi;
  199. //gpr->rax = regs->orig_rax;
  200. gpr->rip = regs->rip;
  201. gpr->rflags = regs->eflags;
  202. gpr->rsp = regs->rsp;
  203. }
  204. static
  205. int host_peekuser (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  206. struct user * ud)
  207. {
  208. sgx_arch_gpr_t gpr;
  209. int ret;
  210. ret = host_peekgpr(memdev, pid, ei, &gpr);
  211. if (ret < 0)
  212. return ret;
  213. fill_regs(&ud->regs, &gpr);
  214. return 0;
  215. }
  216. static
  217. int host_pokeuser (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  218. const struct user * ud)
  219. {
  220. sgx_arch_gpr_t gpr;
  221. int ret;
  222. ret = host_peekgpr(memdev, pid, ei, &gpr);
  223. if (ret < 0)
  224. return ret;
  225. fill_gpr(&gpr, &ud->regs);
  226. return host_pokegpr(memdev, pid, ei, &gpr);
  227. }
  228. static
  229. int host_peekregs (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  230. struct user_regs_struct * regdata)
  231. {
  232. sgx_arch_gpr_t gpr;
  233. int ret;
  234. ret = host_peekgpr(memdev, pid, ei, &gpr);
  235. if (ret < 0)
  236. return ret;
  237. fill_regs(regdata, &gpr);
  238. return 0;
  239. }
  240. static
  241. int host_pokeregs (int memdev, pid_t pid, struct enclave_dbginfo * ei,
  242. const struct user_regs_struct * regdata)
  243. {
  244. sgx_arch_gpr_t gpr;
  245. int ret;
  246. ret = host_peekgpr(memdev, pid, ei, &gpr);
  247. if (ret < 0)
  248. return ret;
  249. fill_gpr(&gpr, regdata);
  250. return host_pokegpr(memdev, pid, ei, &gpr);
  251. }
  252. static struct {
  253. struct enclave_dbginfo ei;
  254. pid_t pid;
  255. int memdev;
  256. } memdevs[32];
  257. static int nmemdevs = 0;
  258. int open_memdevice (pid_t pid, int * memdev, struct enclave_dbginfo ** ei)
  259. {
  260. for (int i = 0 ; i < nmemdevs ; i++)
  261. if (memdevs[i].pid == pid) {
  262. *memdev = memdevs[i].memdev;
  263. *ei = &memdevs[i].ei;
  264. return 0;
  265. }
  266. if (nmemdevs == sizeof(memdevs) / sizeof(memdevs[0]))
  267. return -ENOMEM;
  268. struct enclave_dbginfo eib;
  269. long int res;
  270. for (int off = 0 ; off < sizeof(eib) ; off += sizeof(long int)) {
  271. res = host_ptrace(PTRACE_PEEKDATA, pid,
  272. (void *) DBGINFO_ADDR + off, NULL);
  273. if (errno) {
  274. DEBUG("Failed getting debug information\n");
  275. return -1;
  276. }
  277. *(long int *) ((void *) &eib + off) = res;
  278. }
  279. for (int i = 0 ; i < nmemdevs ; i++)
  280. if (memdevs[i].pid == eib.pid) {
  281. *memdev = memdevs[i].memdev;
  282. *ei = &memdevs[i].ei;
  283. return 0;
  284. }
  285. DEBUG("Retrieved enclave information (PID %d)\n", eib.pid);
  286. char memdev_path[40];
  287. int fd;
  288. snprintf(memdev_path, 40, "/proc/%d/mem", pid);
  289. fd = open(memdev_path, O_RDWR);
  290. if (fd < 0)
  291. return fd;
  292. /* setting debug bit in TCS flags */
  293. for (int i = 0 ; i < MAX_DBG_THREADS ; i++)
  294. if (eib.tcs_addrs[i]) {
  295. void * addr = eib.tcs_addrs[i] + offsetof(sgx_arch_tcs_t, flags);
  296. uint64_t flags;
  297. int ret;
  298. ret = pread(fd, &flags, sizeof(flags), (off_t) addr);
  299. if (ret < sizeof(flags)) {
  300. errno = -EFAULT;
  301. return -1;
  302. }
  303. if (flags & TCS_FLAGS_DBGOPTIN) continue;
  304. flags |= TCS_FLAGS_DBGOPTIN;
  305. DEBUG("set TCS debug flag at %p (%lx)\n", addr, flags);
  306. ret = pwrite(fd, &flags, sizeof(flags), (off_t) addr);
  307. if (ret < sizeof(flags)) {
  308. errno = -EFAULT;
  309. return -1;
  310. }
  311. }
  312. eib.thread_stepping = 0;
  313. memdevs[nmemdevs].pid = pid;
  314. memdevs[nmemdevs].memdev = fd;
  315. memdevs[nmemdevs].ei = eib;
  316. *memdev = fd;
  317. *ei = &memdevs[nmemdevs].ei;
  318. nmemdevs++;
  319. return 0;
  320. }
  321. static inline
  322. int host_peekisinenclave (pid_t pid, struct enclave_dbginfo * ei)
  323. {
  324. struct user_regs_struct regs;
  325. int ret = host_ptrace(PTRACE_GETREGS, pid, NULL, &regs);
  326. if (ret < 0) {
  327. DEBUG("Failed getting registers: PID %d\n", pid);
  328. return ret;
  329. }
  330. DEBUG("%d: User RIP 0x%08llx%s\n", pid, regs.rip,
  331. ((void *) regs.rip == ei->aep) ? " (in enclave)" : "");
  332. return ((void *) regs.rip == ei->aep) ? 1 : 0;
  333. }
  334. long int ptrace (enum __ptrace_request request, ...)
  335. {
  336. long int ret = 0, res;
  337. va_list ap;
  338. pid_t pid;
  339. void * addr, * data;
  340. int memdev;
  341. struct enclave_dbginfo * ei;
  342. int prev_errno = errno;
  343. va_start(ap, request);
  344. pid = va_arg(ap, pid_t);
  345. addr = va_arg(ap, void *);
  346. data = va_arg(ap, void *);
  347. va_end(ap);
  348. ret = open_memdevice(pid, &memdev, &ei);
  349. if (ret < 0)
  350. goto do_host_ptrace;
  351. switch (request) {
  352. case PTRACE_PEEKTEXT:
  353. case PTRACE_PEEKDATA: {
  354. DEBUG("%d: PEEKTEXT/PEEKDATA(%d, %p)\n", getpid(), pid, addr);
  355. if (addr < (void *) ei->base ||
  356. addr >= (void *) (ei->base + ei->size)) {
  357. ret = host_ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
  358. break;
  359. }
  360. ret = pread(memdev, &res, sizeof(long int), (unsigned long) addr);
  361. if (ret >= 0)
  362. ret = res;
  363. break;
  364. }
  365. case PTRACE_POKETEXT:
  366. case PTRACE_POKEDATA: {
  367. DEBUG("%d: POKETEXT/POKEDATA(%d, %p, 0x%016lx)\n", getpid(), pid,
  368. addr, (long int) data);
  369. if (addr < (void *) ei->base ||
  370. addr >= (void *) (ei->base + ei->size)) {
  371. errno = 0;
  372. ret = host_ptrace(PTRACE_POKEDATA, pid, addr, data);
  373. break;
  374. }
  375. ret = pwrite(memdev, &data, sizeof(long int), (off_t) addr);
  376. break;
  377. }
  378. case PTRACE_PEEKUSER: {
  379. struct user userdata;
  380. if ((off_t) addr >= sizeof(struct user)) {
  381. ret = -EINVAL;
  382. break;
  383. }
  384. if (host_peekonetid(pid, memdev, ei) < 0)
  385. goto do_host_ptrace;
  386. ret = host_peekisinenclave(pid, ei);
  387. assert(ret >= 0);
  388. if (!ret)
  389. goto do_host_ptrace;
  390. DEBUG("%d: PEEKUSER(%d, %ld)\n", getpid(), pid, (off_t) addr);
  391. if ((off_t) addr >= sizeof(struct user_regs_struct)) {
  392. errno = 0;
  393. ret = host_ptrace(PTRACE_PEEKUSER, pid, addr, NULL);
  394. break;
  395. }
  396. ret = host_peekuser(memdev, pid, ei, &userdata);
  397. if (ret < 0)
  398. break;
  399. ret = *(long int *)((void *) &userdata + (off_t) addr);
  400. break;
  401. }
  402. case PTRACE_POKEUSER: {
  403. struct user userdata;
  404. if ((off_t) addr >= sizeof(struct user)) {
  405. ret = -EINVAL;
  406. break;
  407. }
  408. if (host_peekonetid(pid, memdev, ei) < 0)
  409. goto do_host_ptrace;
  410. ret = host_peekisinenclave(pid, ei);
  411. assert(ret >= 0);
  412. if (!ret)
  413. goto do_host_ptrace;
  414. DEBUG("%d: POKEUSER(%d, %lx)\n", getpid(), pid, (off_t) addr);
  415. if ((off_t) addr >= sizeof(struct user_regs_struct)) {
  416. ret = host_ptrace(PTRACE_POKEUSER, pid, addr, data);
  417. break;
  418. }
  419. ret = host_peekuser(memdev, pid, ei, &userdata);
  420. if (ret < 0)
  421. break;
  422. *(long int *)((void *) &userdata + (off_t) addr) = (long int) data;
  423. ret = host_pokeuser(memdev, pid, ei, &userdata);
  424. break;
  425. }
  426. case PTRACE_GETREGS: {
  427. if (host_peekonetid(pid, memdev, ei) < 0)
  428. goto do_host_ptrace;
  429. ret = host_peekisinenclave(pid, ei);
  430. assert(ret >= 0);
  431. if (!ret)
  432. goto do_host_ptrace;
  433. DEBUG("%d: GETREGS(%d, %p)\n", getpid(), pid, data);
  434. ret = host_peekregs(memdev, pid, ei,
  435. (struct user_regs_struct *) data);
  436. break;
  437. }
  438. case PTRACE_SETREGS: {
  439. if (host_peekonetid(pid, memdev, ei) < 0)
  440. goto do_host_ptrace;
  441. ret = host_peekisinenclave(pid, ei);
  442. assert(ret >= 0);
  443. if (!ret)
  444. goto do_host_ptrace;
  445. DEBUG("%d: SETREGS(%d, %p)\n", getpid(), pid, data);
  446. ret = host_pokeregs(memdev, pid, ei,
  447. (struct user_regs_struct *) data);
  448. break;
  449. }
  450. case PTRACE_SINGLESTEP: {
  451. if (host_peekonetid(pid, memdev, ei) < 0)
  452. goto do_host_ptrace;
  453. ret = host_peekisinenclave(pid, ei);
  454. assert(ret >= 0);
  455. if (!ret)
  456. goto do_host_ptrace;
  457. for (int i = 0 ; i < MAX_DBG_THREADS ; i++)
  458. if (ei->thread_tids[i] == pid) {
  459. ei->thread_stepping |= 1ULL << i;
  460. break;
  461. }
  462. DEBUG("%d: SINGLESTEP(%d)\n", getpid(), pid);
  463. goto do_host_ptrace;
  464. }
  465. default:
  466. if (request >= 0x4000)
  467. DEBUG("*** bypassed ptrace call: 0x%x ***\n", request);
  468. else
  469. DEBUG("*** bypassed ptrace call: %d ***\n", request);
  470. do_host_ptrace:
  471. errno = prev_errno;
  472. ret = host_ptrace(request, pid, addr, data);
  473. break;
  474. }
  475. if ((request > 0 && request < 4) ? errno : (ret < 0)) {
  476. if (request >= 0x4000)
  477. DEBUG(">>> ptrace(0x%x, %d, %p, %p) = err %d\n", request, pid, addr,
  478. data, errno);
  479. else
  480. DEBUG(">>> ptrace(%d, %d, %p, %p) = err %d\n", request, pid, addr,
  481. data, errno);
  482. } else {
  483. if (request >= 0x4000)
  484. DEBUG(">>> ptrace(0x%x, %d, %p, %p) = 0x%lx\n", request, pid, addr,
  485. data, ret);
  486. else
  487. DEBUG(">>> ptrace(%d, %d, %p, %p) = 0x%lx\n", request, pid, addr,
  488. data, ret);
  489. }
  490. return ret;
  491. }
  492. pid_t waitpid(pid_t pid, int *status, int options)
  493. {
  494. pid_t res;
  495. DEBUG("%d: waitpid(%d)\n", getpid(), pid);
  496. res = syscall((long int) SYS_wait4,
  497. (long int) pid,
  498. (long int) status,
  499. (long int) options,
  500. (long int) NULL);
  501. if (res == -1 || !status)
  502. return res;
  503. if (WIFSTOPPED(*status) && WSTOPSIG(*status) == SIGTRAP) {
  504. int memdev;
  505. struct enclave_dbginfo * ei;
  506. int ret;
  507. pid = res;
  508. ret = open_memdevice(pid, &memdev, &ei);
  509. if (ret < 0)
  510. goto out;
  511. if (host_peekonetid(pid, memdev, ei) < 0)
  512. goto out;
  513. for (int i = 0 ; i < MAX_DBG_THREADS ; i++)
  514. if (ei->thread_tids[i] == pid) {
  515. if (ei->thread_stepping & (1ULL << i)) {
  516. ei->thread_stepping &= ~(1ULL << i);
  517. goto out;
  518. }
  519. goto cont;
  520. }
  521. DEBUG("no this thread: %d\n", pid);
  522. goto out;
  523. cont:
  524. /* if the target thread is inside the enclave */
  525. ret = host_peekisinenclave(pid, ei);
  526. assert(ret >= 0);
  527. if (ret) {
  528. sgx_arch_gpr_t gpr;
  529. uint8_t code;
  530. ret = host_peekgpr(memdev, pid, ei, &gpr);
  531. if (ret < 0)
  532. goto out;
  533. ret = pread(memdev, &code, sizeof(code), (off_t) gpr.rip);
  534. if (ret < 0)
  535. goto out;
  536. if (code != 0xcc)
  537. goto out;
  538. DEBUG("rip 0x%lx points to a breakpoint\n", gpr.rip);
  539. gpr.rip++;
  540. ret = host_pokegpr(memdev, pid, ei, &gpr);
  541. if (ret < 0)
  542. goto out;
  543. }
  544. }
  545. out:
  546. return res;
  547. }