62b65aae12e66271b48f6c5827babf22b282b1a1
[openocd.git] / src / rtos / rtos.c
1 /***************************************************************************
2 * Copyright (C) 2011 by Broadcom Corporation *
3 * Evan Hunter - ehunter@broadcom.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "rtos.h"
24 #include "target/target.h"
25 #include "helper/log.h"
26 #include "helper/binarybuffer.h"
27 #include "server/gdb_server.h"
28
29 /* RTOSs */
30 extern struct rtos_type FreeRTOS_rtos;
31 extern struct rtos_type ThreadX_rtos;
32 extern struct rtos_type eCos_rtos;
33 extern struct rtos_type Linux_os;
34 extern struct rtos_type chibios_rtos;
35 extern struct rtos_type chromium_ec_rtos;
36 extern struct rtos_type embKernel_rtos;
37 extern struct rtos_type mqx_rtos;
38 extern struct rtos_type uCOS_III_rtos;
39 extern struct rtos_type nuttx_rtos;
40 extern struct rtos_type hwthread_rtos;
41 extern struct rtos_type riot_rtos;
42
43 static struct rtos_type *rtos_types[] = {
44 &ThreadX_rtos,
45 &FreeRTOS_rtos,
46 &eCos_rtos,
47 &Linux_os,
48 &chibios_rtos,
49 &chromium_ec_rtos,
50 &embKernel_rtos,
51 &mqx_rtos,
52 &uCOS_III_rtos,
53 &nuttx_rtos,
54 &riot_rtos,
55 /* keep this as last, as it always matches with rtos auto */
56 &hwthread_rtos,
57 NULL
58 };
59
60 int rtos_thread_packet(struct connection *connection, const char *packet, int packet_size);
61
62 int rtos_smp_init(struct target *target)
63 {
64 if (target->rtos->type->smp_init)
65 return target->rtos->type->smp_init(target);
66 return ERROR_TARGET_INIT_FAILED;
67 }
68
69 static int rtos_target_for_threadid(struct connection *connection, int64_t threadid, struct target **t)
70 {
71 struct target *curr = get_target_from_connection(connection);
72 if (t)
73 *t = curr;
74
75 return ERROR_OK;
76 }
77
78 static int os_alloc(struct target *target, struct rtos_type *ostype)
79 {
80 struct rtos *os = target->rtos = calloc(1, sizeof(struct rtos));
81
82 if (!os)
83 return JIM_ERR;
84
85 os->type = ostype;
86 os->current_threadid = -1;
87 os->current_thread = 0;
88 os->symbols = NULL;
89 os->target = target;
90
91 /* RTOS drivers can override the packet handler in _create(). */
92 os->gdb_thread_packet = rtos_thread_packet;
93 os->gdb_target_for_threadid = rtos_target_for_threadid;
94
95 return JIM_OK;
96 }
97
98 static void os_free(struct target *target)
99 {
100 if (!target->rtos)
101 return;
102
103 free(target->rtos->symbols);
104 free(target->rtos);
105 target->rtos = NULL;
106 }
107
108 static int os_alloc_create(struct target *target, struct rtos_type *ostype)
109 {
110 int ret = os_alloc(target, ostype);
111
112 if (JIM_OK == ret) {
113 ret = target->rtos->type->create(target);
114 if (ret != JIM_OK)
115 os_free(target);
116 }
117
118 return ret;
119 }
120
121 int rtos_create(Jim_GetOptInfo *goi, struct target *target)
122 {
123 int x;
124 const char *cp;
125 Jim_Obj *res;
126 int e;
127
128 if (!goi->isconfigure && goi->argc != 0) {
129 Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "NO PARAMS");
130 return JIM_ERR;
131 }
132
133 os_free(target);
134
135 e = Jim_GetOpt_String(goi, &cp, NULL);
136 if (e != JIM_OK)
137 return e;
138
139 if (0 == strcmp(cp, "auto")) {
140 /* Auto detect tries to look up all symbols for each RTOS,
141 * and runs the RTOS driver's _detect() function when GDB
142 * finds all symbols for any RTOS. See rtos_qsymbol(). */
143 target->rtos_auto_detect = true;
144
145 /* rtos_qsymbol() will iterate over all RTOSes. Allocate
146 * target->rtos here, and set it to the first RTOS type. */
147 return os_alloc(target, rtos_types[0]);
148 }
149
150 for (x = 0; rtos_types[x]; x++)
151 if (0 == strcmp(cp, rtos_types[x]->name))
152 return os_alloc_create(target, rtos_types[x]);
153
154 Jim_SetResultFormatted(goi->interp, "Unknown RTOS type %s, try one of: ", cp);
155 res = Jim_GetResult(goi->interp);
156 for (x = 0; rtos_types[x]; x++)
157 Jim_AppendStrings(goi->interp, res, rtos_types[x]->name, ", ", NULL);
158 Jim_AppendStrings(goi->interp, res, " or auto", NULL);
159
160 return JIM_ERR;
161 }
162
163 void rtos_destroy(struct target *target)
164 {
165 os_free(target);
166 }
167
168 int gdb_thread_packet(struct connection *connection, char const *packet, int packet_size)
169 {
170 struct target *target = get_target_from_connection(connection);
171 if (target->rtos == NULL)
172 return rtos_thread_packet(connection, packet, packet_size); /* thread not
173 *found*/
174 return target->rtos->gdb_thread_packet(connection, packet, packet_size);
175 }
176
177 static symbol_table_elem_t *next_symbol(struct rtos *os, char *cur_symbol, uint64_t cur_addr)
178 {
179 symbol_table_elem_t *s;
180
181 if (!os->symbols)
182 os->type->get_symbol_list_to_lookup(&os->symbols);
183
184 if (!cur_symbol[0])
185 return &os->symbols[0];
186
187 for (s = os->symbols; s->symbol_name; s++)
188 if (!strcmp(s->symbol_name, cur_symbol)) {
189 s->address = cur_addr;
190 s++;
191 return s;
192 }
193
194 return NULL;
195 }
196
197 /* searches for 'symbol' in the lookup table for 'os' and returns TRUE,
198 * if 'symbol' is not declared optional */
199 static bool is_symbol_mandatory(const struct rtos *os, const char *symbol)
200 {
201 for (symbol_table_elem_t *s = os->symbols; s->symbol_name; ++s) {
202 if (!strcmp(s->symbol_name, symbol))
203 return !s->optional;
204 }
205 return false;
206 }
207
208 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
209 *
210 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
211 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
212 *
213 * If the qSymbol packet has no address that means GDB did not find the
214 * symbol, in which case auto-detect will move on to try the next RTOS.
215 *
216 * rtos_qsymbol() then calls the next_symbol() helper function, which
217 * iterates over symbol names for the current RTOS until it finds the
218 * symbol in the received GDB packet, and then returns the next entry
219 * in the list of symbols.
220 *
221 * If GDB replied about the last symbol for the RTOS and the RTOS was
222 * specified explicitly, then no further symbol lookup is done. When
223 * auto-detecting, the RTOS driver _detect() function must return success.
224 *
225 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
226 */
227 int rtos_qsymbol(struct connection *connection, char const *packet, int packet_size)
228 {
229 int rtos_detected = 0;
230 uint64_t addr = 0;
231 size_t reply_len;
232 char reply[GDB_BUFFER_SIZE + 1], cur_sym[GDB_BUFFER_SIZE / 2 + 1] = ""; /* Extra byte for null-termination */
233 symbol_table_elem_t *next_sym = NULL;
234 struct target *target = get_target_from_connection(connection);
235 struct rtos *os = target->rtos;
236
237 reply_len = sprintf(reply, "OK");
238
239 if (!os)
240 goto done;
241
242 /* Decode any symbol name in the packet*/
243 size_t len = unhexify((uint8_t *)cur_sym, strchr(packet + 8, ':') + 1, strlen(strchr(packet + 8, ':') + 1));
244 cur_sym[len] = 0;
245
246 if ((strcmp(packet, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
247 (!sscanf(packet, "qSymbol:%" SCNx64 ":", &addr)) && /* GDB did not find an address for a symbol */
248 is_symbol_mandatory(os, cur_sym)) { /* the symbol is mandatory for this RTOS */
249
250 /* GDB could not find an address for the previous symbol */
251 if (!target->rtos_auto_detect) {
252 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os->type->name, cur_sym);
253 goto done;
254 } else {
255 /* Autodetecting RTOS - try next RTOS */
256 if (!rtos_try_next(target)) {
257 LOG_WARNING("No RTOS could be auto-detected!");
258 goto done;
259 }
260
261 /* Next RTOS selected - invalidate current symbol */
262 cur_sym[0] = '\x00';
263 }
264 }
265 next_sym = next_symbol(os, cur_sym, addr);
266
267 if (!next_sym->symbol_name) {
268 /* No more symbols need looking up */
269
270 if (!target->rtos_auto_detect) {
271 rtos_detected = 1;
272 goto done;
273 }
274
275 if (os->type->detect_rtos(target)) {
276 LOG_INFO("Auto-detected RTOS: %s", os->type->name);
277 rtos_detected = 1;
278 goto done;
279 } else {
280 LOG_WARNING("No RTOS could be auto-detected!");
281 goto done;
282 }
283 }
284
285 if (8 + (strlen(next_sym->symbol_name) * 2) + 1 > sizeof(reply)) {
286 LOG_ERROR("ERROR: RTOS symbol '%s' name is too long for GDB!", next_sym->symbol_name);
287 goto done;
288 }
289
290 reply_len = snprintf(reply, sizeof(reply), "qSymbol:");
291 reply_len += hexify(reply + reply_len,
292 (const uint8_t *)next_sym->symbol_name, strlen(next_sym->symbol_name),
293 sizeof(reply) - reply_len);
294
295 done:
296 gdb_put_packet(connection, reply, reply_len);
297 return rtos_detected;
298 }
299
300 int rtos_thread_packet(struct connection *connection, char const *packet, int packet_size)
301 {
302 struct target *target = get_target_from_connection(connection);
303
304 if (strncmp(packet, "qThreadExtraInfo,", 17) == 0) {
305 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL) &&
306 (target->rtos->thread_count != 0)) {
307 threadid_t threadid = 0;
308 int found = -1;
309 sscanf(packet, "qThreadExtraInfo,%" SCNx64, &threadid);
310
311 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
312 int thread_num;
313 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
314 if (target->rtos->thread_details[thread_num].threadid == threadid) {
315 if (target->rtos->thread_details[thread_num].exists)
316 found = thread_num;
317 }
318 }
319 }
320 if (found == -1) {
321 gdb_put_packet(connection, "E01", 3); /* thread not found */
322 return ERROR_OK;
323 }
324
325 struct thread_detail *detail = &target->rtos->thread_details[found];
326
327 int str_size = 0;
328 if (detail->thread_name_str != NULL)
329 str_size += strlen(detail->thread_name_str);
330 if (detail->extra_info_str != NULL)
331 str_size += strlen(detail->extra_info_str);
332
333 char *tmp_str = calloc(str_size + 9, sizeof(char));
334 char *tmp_str_ptr = tmp_str;
335
336 if (detail->thread_name_str != NULL)
337 tmp_str_ptr += sprintf(tmp_str_ptr, "Name: %s", detail->thread_name_str);
338 if (detail->extra_info_str != NULL) {
339 if (tmp_str_ptr != tmp_str)
340 tmp_str_ptr += sprintf(tmp_str_ptr, ", ");
341 tmp_str_ptr += sprintf(tmp_str_ptr, "%s", detail->extra_info_str);
342 }
343
344 assert(strlen(tmp_str) ==
345 (size_t) (tmp_str_ptr - tmp_str));
346
347 char *hex_str = malloc(strlen(tmp_str) * 2 + 1);
348 size_t pkt_len = hexify(hex_str, (const uint8_t *)tmp_str,
349 strlen(tmp_str), strlen(tmp_str) * 2 + 1);
350
351 gdb_put_packet(connection, hex_str, pkt_len);
352 free(hex_str);
353 free(tmp_str);
354 return ERROR_OK;
355
356 }
357 gdb_put_packet(connection, "", 0);
358 return ERROR_OK;
359 } else if (strncmp(packet, "qSymbol", 7) == 0) {
360 if (rtos_qsymbol(connection, packet, packet_size) == 1) {
361 if (target->rtos_auto_detect == true) {
362 target->rtos_auto_detect = false;
363 target->rtos->type->create(target);
364 }
365 target->rtos->type->update_threads(target->rtos);
366 }
367 return ERROR_OK;
368 } else if (strncmp(packet, "qfThreadInfo", 12) == 0) {
369 int i;
370 if (target->rtos != NULL) {
371 if (target->rtos->thread_count == 0) {
372 gdb_put_packet(connection, "l", 1);
373 } else {
374 /*thread id are 16 char +1 for ',' */
375 char *out_str = malloc(17 * target->rtos->thread_count + 1);
376 char *tmp_str = out_str;
377 for (i = 0; i < target->rtos->thread_count; i++) {
378 tmp_str += sprintf(tmp_str, "%c%016" PRIx64, i == 0 ? 'm' : ',',
379 target->rtos->thread_details[i].threadid);
380 }
381 gdb_put_packet(connection, out_str, strlen(out_str));
382 free(out_str);
383 }
384 } else
385 gdb_put_packet(connection, "l", 1);
386
387 return ERROR_OK;
388 } else if (strncmp(packet, "qsThreadInfo", 12) == 0) {
389 gdb_put_packet(connection, "l", 1);
390 return ERROR_OK;
391 } else if (strncmp(packet, "qAttached", 9) == 0) {
392 gdb_put_packet(connection, "1", 1);
393 return ERROR_OK;
394 } else if (strncmp(packet, "qOffsets", 8) == 0) {
395 char offsets[] = "Text=0;Data=0;Bss=0";
396 gdb_put_packet(connection, offsets, sizeof(offsets)-1);
397 return ERROR_OK;
398 } else if (strncmp(packet, "qCRC:", 5) == 0) {
399 /* make sure we check this before "qC" packet below
400 * otherwise it gets incorrectly handled */
401 return GDB_THREAD_PACKET_NOT_CONSUMED;
402 } else if (strncmp(packet, "qC", 2) == 0) {
403 if (target->rtos != NULL) {
404 char buffer[19];
405 int size;
406 size = snprintf(buffer, 19, "QC%016" PRIx64, target->rtos->current_thread);
407 gdb_put_packet(connection, buffer, size);
408 } else
409 gdb_put_packet(connection, "QC0", 3);
410 return ERROR_OK;
411 } else if (packet[0] == 'T') { /* Is thread alive? */
412 threadid_t threadid;
413 int found = -1;
414 sscanf(packet, "T%" SCNx64, &threadid);
415 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
416 int thread_num;
417 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
418 if (target->rtos->thread_details[thread_num].threadid == threadid) {
419 if (target->rtos->thread_details[thread_num].exists)
420 found = thread_num;
421 }
422 }
423 }
424 if (found != -1)
425 gdb_put_packet(connection, "OK", 2); /* thread alive */
426 else
427 gdb_put_packet(connection, "E01", 3); /* thread not found */
428 return ERROR_OK;
429 } else if (packet[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
430 * all other operations ) */
431 if ((packet[1] == 'g') && (target->rtos != NULL)) {
432 threadid_t threadid;
433 sscanf(packet, "Hg%16" SCNx64, &threadid);
434 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64, threadid);
435 /* threadid of 0 indicates target should choose */
436 if (threadid == 0)
437 target->rtos->current_threadid = target->rtos->current_thread;
438 else
439 target->rtos->current_threadid = threadid;
440 }
441 gdb_put_packet(connection, "OK", 2);
442 return ERROR_OK;
443 }
444
445 return GDB_THREAD_PACKET_NOT_CONSUMED;
446 }
447
448 static int rtos_put_gdb_reg_list(struct connection *connection,
449 struct rtos_reg *reg_list, int num_regs)
450 {
451 size_t num_bytes = 1; /* NUL */
452 for (int i = 0; i < num_regs; ++i)
453 num_bytes += DIV_ROUND_UP(reg_list[i].size, 8) * 2;
454
455 char *hex = malloc(num_bytes);
456 char *hex_p = hex;
457
458 for (int i = 0; i < num_regs; ++i) {
459 size_t count = DIV_ROUND_UP(reg_list[i].size, 8);
460 size_t n = hexify(hex_p, reg_list[i].value, count, num_bytes);
461 hex_p += n;
462 num_bytes -= n;
463 }
464
465 gdb_put_packet(connection, hex, strlen(hex));
466 free(hex);
467
468 return ERROR_OK;
469 }
470
471 /** Look through all registers to find this register. */
472 int rtos_get_gdb_reg(struct connection *connection, int reg_num)
473 {
474 struct target *target = get_target_from_connection(connection);
475 int64_t current_threadid = target->rtos->current_threadid;
476 if ((target->rtos != NULL) && (current_threadid != -1) &&
477 (current_threadid != 0) &&
478 ((current_threadid != target->rtos->current_thread) ||
479 (target->smp))) { /* in smp several current thread are possible */
480 struct rtos_reg *reg_list;
481 int num_regs;
482
483 LOG_DEBUG("getting register %d for thread 0x%" PRIx64
484 ", target->rtos->current_thread=0x%" PRIx64,
485 reg_num,
486 current_threadid,
487 target->rtos->current_thread);
488
489 int retval;
490 if (target->rtos->type->get_thread_reg) {
491 reg_list = calloc(1, sizeof(*reg_list));
492 num_regs = 1;
493 retval = target->rtos->type->get_thread_reg(target->rtos,
494 current_threadid, reg_num, &reg_list[0]);
495 if (retval != ERROR_OK) {
496 LOG_ERROR("RTOS: failed to get register %d", reg_num);
497 return retval;
498 }
499 } else {
500 retval = target->rtos->type->get_thread_reg_list(target->rtos,
501 current_threadid,
502 &reg_list,
503 &num_regs);
504 if (retval != ERROR_OK) {
505 LOG_ERROR("RTOS: failed to get register list");
506 return retval;
507 }
508 }
509
510 for (int i = 0; i < num_regs; ++i) {
511 if (reg_list[i].number == (uint32_t)reg_num) {
512 rtos_put_gdb_reg_list(connection, reg_list + i, 1);
513 free(reg_list);
514 return ERROR_OK;
515 }
516 }
517
518 free(reg_list);
519 }
520 return ERROR_FAIL;
521 }
522
523 /** Return a list of general registers. */
524 int rtos_get_gdb_reg_list(struct connection *connection)
525 {
526 struct target *target = get_target_from_connection(connection);
527 int64_t current_threadid = target->rtos->current_threadid;
528 if ((target->rtos != NULL) && (current_threadid != -1) &&
529 (current_threadid != 0) &&
530 ((current_threadid != target->rtos->current_thread) ||
531 (target->smp))) { /* in smp several current thread are possible */
532 struct rtos_reg *reg_list;
533 int num_regs;
534
535 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
536 ", target->rtos->current_thread=0x%" PRIx64 "\r\n",
537 current_threadid,
538 target->rtos->current_thread);
539
540 int retval = target->rtos->type->get_thread_reg_list(target->rtos,
541 current_threadid,
542 &reg_list,
543 &num_regs);
544 if (retval != ERROR_OK) {
545 LOG_ERROR("RTOS: failed to get register list");
546 return retval;
547 }
548
549 rtos_put_gdb_reg_list(connection, reg_list, num_regs);
550 free(reg_list);
551
552 return ERROR_OK;
553 }
554 return ERROR_FAIL;
555 }
556
557 int rtos_set_reg(struct connection *connection, int reg_num,
558 uint8_t *reg_value)
559 {
560 struct target *target = get_target_from_connection(connection);
561 int64_t current_threadid = target->rtos->current_threadid;
562 if ((target->rtos != NULL) &&
563 (target->rtos->type->set_reg != NULL) &&
564 (current_threadid != -1) &&
565 (current_threadid != 0)) {
566 return target->rtos->type->set_reg(target->rtos, reg_num, reg_value);
567 }
568 return ERROR_FAIL;
569 }
570
571 int rtos_generic_stack_read(struct target *target,
572 const struct rtos_register_stacking *stacking,
573 int64_t stack_ptr,
574 struct rtos_reg **reg_list,
575 int *num_regs)
576 {
577 int retval;
578
579 if (stack_ptr == 0) {
580 LOG_ERROR("Error: null stack pointer in thread");
581 return -5;
582 }
583 /* Read the stack */
584 uint8_t *stack_data = malloc(stacking->stack_registers_size);
585 uint32_t address = stack_ptr;
586
587 if (stacking->stack_growth_direction == 1)
588 address -= stacking->stack_registers_size;
589 retval = target_read_buffer(target, address, stacking->stack_registers_size, stack_data);
590 if (retval != ERROR_OK) {
591 free(stack_data);
592 LOG_ERROR("Error reading stack frame from thread");
593 return retval;
594 }
595 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32, address);
596
597 #if 0
598 LOG_OUTPUT("Stack Data :");
599 for (i = 0; i < stacking->stack_registers_size; i++)
600 LOG_OUTPUT("%02X", stack_data[i]);
601 LOG_OUTPUT("\r\n");
602 #endif
603
604 int64_t new_stack_ptr;
605 if (stacking->calculate_process_stack != NULL) {
606 new_stack_ptr = stacking->calculate_process_stack(target,
607 stack_data, stacking, stack_ptr);
608 } else {
609 new_stack_ptr = stack_ptr - stacking->stack_growth_direction *
610 stacking->stack_registers_size;
611 }
612
613 *reg_list = calloc(stacking->num_output_registers, sizeof(struct rtos_reg));
614 *num_regs = stacking->num_output_registers;
615
616 for (int i = 0; i < stacking->num_output_registers; ++i) {
617 (*reg_list)[i].number = stacking->register_offsets[i].number;
618 (*reg_list)[i].size = stacking->register_offsets[i].width_bits;
619
620 int offset = stacking->register_offsets[i].offset;
621 if (offset == -2)
622 buf_cpy(&new_stack_ptr, (*reg_list)[i].value, (*reg_list)[i].size);
623 else if (offset != -1)
624 buf_cpy(stack_data + offset, (*reg_list)[i].value, (*reg_list)[i].size);
625 }
626
627 free(stack_data);
628 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
629 return ERROR_OK;
630 }
631
632 int rtos_try_next(struct target *target)
633 {
634 struct rtos *os = target->rtos;
635 struct rtos_type **type = rtos_types;
636
637 if (!os)
638 return 0;
639
640 while (*type && os->type != *type)
641 type++;
642
643 if (!*type || !*(++type))
644 return 0;
645
646 os->type = *type;
647
648 free(os->symbols);
649 os->symbols = NULL;
650
651 return 1;
652 }
653
654 int rtos_update_threads(struct target *target)
655 {
656 if ((target->rtos != NULL) && (target->rtos->type != NULL))
657 target->rtos->type->update_threads(target->rtos);
658 return ERROR_OK;
659 }
660
661 void rtos_free_threadlist(struct rtos *rtos)
662 {
663 if (rtos->thread_details) {
664 int j;
665
666 for (j = 0; j < rtos->thread_count; j++) {
667 struct thread_detail *current_thread = &rtos->thread_details[j];
668 free(current_thread->thread_name_str);
669 free(current_thread->extra_info_str);
670 }
671 free(rtos->thread_details);
672 rtos->thread_details = NULL;
673 rtos->thread_count = 0;
674 rtos->current_threadid = -1;
675 rtos->current_thread = 0;
676 }
677 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)