1 // SPDX-License-Identifier: GPL-2.0-or-later
3 /***************************************************************************
4 * Copyright (C) 2011 by Broadcom Corporation *
5 * Evan Hunter - ehunter@broadcom.com *
6 ***************************************************************************/
13 #include "target/target.h"
14 #include "helper/log.h"
15 #include "helper/binarybuffer.h"
16 #include "server/gdb_server.h"
18 static const struct rtos_type
*rtos_types
[] = {
32 /* keep this as last, as it always matches with rtos auto */
37 static int rtos_try_next(struct target
*target
);
39 int rtos_thread_packet(struct connection
*connection
, const char *packet
, int packet_size
);
41 int rtos_smp_init(struct target
*target
)
43 if (target
->rtos
->type
->smp_init
)
44 return target
->rtos
->type
->smp_init(target
);
45 return ERROR_TARGET_INIT_FAILED
;
48 static int rtos_target_for_threadid(struct connection
*connection
, int64_t threadid
, struct target
**t
)
50 struct target
*curr
= get_target_from_connection(connection
);
57 static int os_alloc(struct target
*target
, const struct rtos_type
*ostype
)
59 struct rtos
*os
= target
->rtos
= calloc(1, sizeof(struct rtos
));
65 os
->current_threadid
= -1;
66 os
->current_thread
= 0;
70 /* RTOS drivers can override the packet handler in _create(). */
71 os
->gdb_thread_packet
= rtos_thread_packet
;
72 os
->gdb_target_for_threadid
= rtos_target_for_threadid
;
77 static void os_free(struct target
*target
)
82 free(target
->rtos
->symbols
);
87 static int os_alloc_create(struct target
*target
, const struct rtos_type
*ostype
)
89 int ret
= os_alloc(target
, ostype
);
92 ret
= target
->rtos
->type
->create(target
);
100 int rtos_create(struct jim_getopt_info
*goi
, struct target
*target
)
107 if (!goi
->isconfigure
&& goi
->argc
!= 0) {
108 Jim_WrongNumArgs(goi
->interp
, goi
->argc
, goi
->argv
, "NO PARAMS");
114 e
= jim_getopt_string(goi
, &cp
, NULL
);
118 if (strcmp(cp
, "none") == 0)
121 if (strcmp(cp
, "auto") == 0) {
122 /* Auto detect tries to look up all symbols for each RTOS,
123 * and runs the RTOS driver's _detect() function when GDB
124 * finds all symbols for any RTOS. See rtos_qsymbol(). */
125 target
->rtos_auto_detect
= true;
127 /* rtos_qsymbol() will iterate over all RTOSes. Allocate
128 * target->rtos here, and set it to the first RTOS type. */
129 return os_alloc(target
, rtos_types
[0]);
132 for (x
= 0; rtos_types
[x
]; x
++)
133 if (strcmp(cp
, rtos_types
[x
]->name
) == 0)
134 return os_alloc_create(target
, rtos_types
[x
]);
136 Jim_SetResultFormatted(goi
->interp
, "Unknown RTOS type %s, try one of: ", cp
);
137 res
= Jim_GetResult(goi
->interp
);
138 for (x
= 0; rtos_types
[x
]; x
++)
139 Jim_AppendStrings(goi
->interp
, res
, rtos_types
[x
]->name
, ", ", NULL
);
140 Jim_AppendStrings(goi
->interp
, res
, ", auto or none", NULL
);
145 void rtos_destroy(struct target
*target
)
150 int gdb_thread_packet(struct connection
*connection
, char const *packet
, int packet_size
)
152 struct target
*target
= get_target_from_connection(connection
);
154 return rtos_thread_packet(connection
, packet
, packet_size
); /* thread not
156 return target
->rtos
->gdb_thread_packet(connection
, packet
, packet_size
);
159 static struct symbol_table_elem
*find_symbol(const struct rtos
*os
, const char *symbol
)
161 struct symbol_table_elem
*s
;
163 for (s
= os
->symbols
; s
->symbol_name
; s
++)
164 if (!strcmp(s
->symbol_name
, symbol
))
170 static struct symbol_table_elem
*next_symbol(struct rtos
*os
, char *cur_symbol
, uint64_t cur_addr
)
173 os
->type
->get_symbol_list_to_lookup(&os
->symbols
);
176 return &os
->symbols
[0];
178 struct symbol_table_elem
*s
= find_symbol(os
, cur_symbol
);
182 s
->address
= cur_addr
;
187 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
189 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
190 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
192 * If the qSymbol packet has no address that means GDB did not find the
193 * symbol, in which case auto-detect will move on to try the next RTOS.
195 * rtos_qsymbol() then calls the next_symbol() helper function, which
196 * iterates over symbol names for the current RTOS until it finds the
197 * symbol in the received GDB packet, and then returns the next entry
198 * in the list of symbols.
200 * If GDB replied about the last symbol for the RTOS and the RTOS was
201 * specified explicitly, then no further symbol lookup is done. When
202 * auto-detecting, the RTOS driver _detect() function must return success.
204 * The symbol is tried twice to handle the -flto case with gcc. The first
205 * attempt uses the symbol as-is, and the second attempt tries the symbol
206 * with ".lto_priv.0" appended to it. We only consider the first static
207 * symbol here from the -flto case. (Each subsequent static symbol with
208 * the same name is exported as .lto_priv.1, .lto_priv.2, etc.)
210 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
212 int rtos_qsymbol(struct connection
*connection
, char const *packet
, int packet_size
)
214 int rtos_detected
= 0;
217 char reply
[GDB_BUFFER_SIZE
+ 1], cur_sym
[GDB_BUFFER_SIZE
/ 2 + 1] = ""; /* Extra byte for null-termination */
218 struct symbol_table_elem
*next_sym
= NULL
;
219 struct target
*target
= get_target_from_connection(connection
);
220 struct rtos
*os
= target
->rtos
;
222 reply_len
= sprintf(reply
, "OK");
227 /* Decode any symbol name in the packet*/
228 size_t len
= unhexify((uint8_t *)cur_sym
, strchr(packet
+ 8, ':') + 1, strlen(strchr(packet
+ 8, ':') + 1));
231 const char no_suffix
[] = "";
232 const char lto_suffix
[] = ".lto_priv.0";
233 const size_t lto_suffix_len
= strlen(lto_suffix
);
235 const char *cur_suffix
;
236 const char *next_suffix
;
238 /* Detect what suffix was used during the previous symbol lookup attempt, and
239 * speculatively determine the next suffix (only used for the unknown address case) */
240 if (len
> lto_suffix_len
&& !strcmp(cur_sym
+ len
- lto_suffix_len
, lto_suffix
)) {
241 /* Trim the suffix from cur_sym for comparison purposes below */
242 cur_sym
[len
- lto_suffix_len
] = '\0';
243 cur_suffix
= lto_suffix
;
246 cur_suffix
= no_suffix
;
247 next_suffix
= lto_suffix
;
250 if ((strcmp(packet
, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
251 (!sscanf(packet
, "qSymbol:%" SCNx64
":", &addr
))) { /* GDB did not find an address for a symbol */
253 /* GDB could not find an address for the previous symbol */
254 struct symbol_table_elem
*sym
= find_symbol(os
, cur_sym
);
258 } else if (sym
&& !sym
->optional
) { /* the symbol is mandatory for this RTOS */
259 if (!target
->rtos_auto_detect
) {
260 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os
->type
->name
, cur_sym
);
263 /* Autodetecting RTOS - try next RTOS */
264 if (!rtos_try_next(target
)) {
265 LOG_WARNING("No RTOS could be auto-detected!");
269 /* Next RTOS selected - invalidate current symbol */
275 LOG_DEBUG("RTOS: Address of symbol '%s%s' is 0x%" PRIx64
, cur_sym
, cur_suffix
, addr
);
278 next_sym
= next_symbol(os
, cur_sym
, addr
);
279 next_suffix
= no_suffix
;
282 /* Should never happen unless the debugger misbehaves */
284 LOG_WARNING("RTOS: Debugger sent us qSymbol with '%s%s' that we did not ask for", cur_sym
, cur_suffix
);
288 if (!next_sym
->symbol_name
) {
289 /* No more symbols need looking up */
291 if (!target
->rtos_auto_detect
) {
296 if (os
->type
->detect_rtos(target
)) {
297 LOG_INFO("Auto-detected RTOS: %s", os
->type
->name
);
301 LOG_WARNING("No RTOS could be auto-detected!");
308 reply_len
= 8; /* snprintf(..., "qSymbol:") */
309 reply_len
+= 2 * strlen(next_sym
->symbol_name
); /* hexify(..., next_sym->symbol_name, ...) */
310 reply_len
+= 2 * strlen(next_suffix
); /* hexify(..., next_suffix, ...) */
311 reply_len
+= 1; /* Terminating NUL */
312 if (reply_len
> sizeof(reply
)) {
313 LOG_ERROR("ERROR: RTOS symbol '%s%s' name is too long for GDB!", next_sym
->symbol_name
, next_suffix
);
317 LOG_DEBUG("RTOS: Requesting symbol lookup of '%s%s' from the debugger", next_sym
->symbol_name
, next_suffix
);
319 reply_len
= snprintf(reply
, sizeof(reply
), "qSymbol:");
320 reply_len
+= hexify(reply
+ reply_len
,
321 (const uint8_t *)next_sym
->symbol_name
, strlen(next_sym
->symbol_name
),
322 sizeof(reply
) - reply_len
);
323 reply_len
+= hexify(reply
+ reply_len
,
324 (const uint8_t *)next_suffix
, strlen(next_suffix
),
325 sizeof(reply
) - reply_len
);
328 gdb_put_packet(connection
, reply
, reply_len
);
329 return rtos_detected
;
332 int rtos_thread_packet(struct connection
*connection
, char const *packet
, int packet_size
)
334 struct target
*target
= get_target_from_connection(connection
);
336 if (strncmp(packet
, "qThreadExtraInfo,", 17) == 0) {
337 if ((target
->rtos
) && (target
->rtos
->thread_details
) &&
338 (target
->rtos
->thread_count
!= 0)) {
339 threadid_t threadid
= 0;
341 sscanf(packet
, "qThreadExtraInfo,%" SCNx64
, &threadid
);
343 if ((target
->rtos
) && (target
->rtos
->thread_details
)) {
345 for (thread_num
= 0; thread_num
< target
->rtos
->thread_count
; thread_num
++) {
346 if (target
->rtos
->thread_details
[thread_num
].threadid
== threadid
) {
347 if (target
->rtos
->thread_details
[thread_num
].exists
)
353 gdb_put_packet(connection
, "E01", 3); /* thread not found */
357 struct thread_detail
*detail
= &target
->rtos
->thread_details
[found
];
360 if (detail
->thread_name_str
)
361 str_size
+= strlen(detail
->thread_name_str
);
362 if (detail
->extra_info_str
)
363 str_size
+= strlen(detail
->extra_info_str
);
365 char *tmp_str
= calloc(str_size
+ 9, sizeof(char));
366 char *tmp_str_ptr
= tmp_str
;
368 if (detail
->thread_name_str
)
369 tmp_str_ptr
+= sprintf(tmp_str_ptr
, "Name: %s", detail
->thread_name_str
);
370 if (detail
->extra_info_str
) {
371 if (tmp_str_ptr
!= tmp_str
)
372 tmp_str_ptr
+= sprintf(tmp_str_ptr
, ", ");
373 tmp_str_ptr
+= sprintf(tmp_str_ptr
, "%s", detail
->extra_info_str
);
376 assert(strlen(tmp_str
) ==
377 (size_t) (tmp_str_ptr
- tmp_str
));
379 char *hex_str
= malloc(strlen(tmp_str
) * 2 + 1);
380 size_t pkt_len
= hexify(hex_str
, (const uint8_t *)tmp_str
,
381 strlen(tmp_str
), strlen(tmp_str
) * 2 + 1);
383 gdb_put_packet(connection
, hex_str
, pkt_len
);
389 gdb_put_packet(connection
, "", 0);
391 } else if (strncmp(packet
, "qSymbol", 7) == 0) {
392 if (rtos_qsymbol(connection
, packet
, packet_size
) == 1) {
393 if (target
->rtos_auto_detect
== true) {
394 target
->rtos_auto_detect
= false;
395 target
->rtos
->type
->create(target
);
397 target
->rtos
->type
->update_threads(target
->rtos
);
400 } else if (strncmp(packet
, "qfThreadInfo", 12) == 0) {
403 if (target
->rtos
->thread_count
== 0) {
404 gdb_put_packet(connection
, "l", 1);
406 /*thread id are 16 char +1 for ',' */
407 char *out_str
= malloc(17 * target
->rtos
->thread_count
+ 1);
408 char *tmp_str
= out_str
;
409 for (i
= 0; i
< target
->rtos
->thread_count
; i
++) {
410 tmp_str
+= sprintf(tmp_str
, "%c%016" PRIx64
, i
== 0 ? 'm' : ',',
411 target
->rtos
->thread_details
[i
].threadid
);
413 gdb_put_packet(connection
, out_str
, strlen(out_str
));
417 gdb_put_packet(connection
, "l", 1);
420 } else if (strncmp(packet
, "qsThreadInfo", 12) == 0) {
421 gdb_put_packet(connection
, "l", 1);
423 } else if (strncmp(packet
, "qAttached", 9) == 0) {
424 gdb_put_packet(connection
, "1", 1);
426 } else if (strncmp(packet
, "qOffsets", 8) == 0) {
427 char offsets
[] = "Text=0;Data=0;Bss=0";
428 gdb_put_packet(connection
, offsets
, sizeof(offsets
)-1);
430 } else if (strncmp(packet
, "qCRC:", 5) == 0) {
431 /* make sure we check this before "qC" packet below
432 * otherwise it gets incorrectly handled */
433 return GDB_THREAD_PACKET_NOT_CONSUMED
;
434 } else if (strncmp(packet
, "qC", 2) == 0) {
438 size
= snprintf(buffer
, 19, "QC%016" PRIx64
, target
->rtos
->current_thread
);
439 gdb_put_packet(connection
, buffer
, size
);
441 gdb_put_packet(connection
, "QC0", 3);
443 } else if (packet
[0] == 'T') { /* Is thread alive? */
446 sscanf(packet
, "T%" SCNx64
, &threadid
);
447 if ((target
->rtos
) && (target
->rtos
->thread_details
)) {
449 for (thread_num
= 0; thread_num
< target
->rtos
->thread_count
; thread_num
++) {
450 if (target
->rtos
->thread_details
[thread_num
].threadid
== threadid
) {
451 if (target
->rtos
->thread_details
[thread_num
].exists
)
457 gdb_put_packet(connection
, "OK", 2); /* thread alive */
459 gdb_put_packet(connection
, "E01", 3); /* thread not found */
461 } else if (packet
[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
462 * all other operations ) */
463 if ((packet
[1] == 'g') && (target
->rtos
)) {
465 sscanf(packet
, "Hg%16" SCNx64
, &threadid
);
466 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64
, threadid
);
467 /* threadid of 0 indicates target should choose */
469 target
->rtos
->current_threadid
= target
->rtos
->current_thread
;
471 target
->rtos
->current_threadid
= threadid
;
473 gdb_put_packet(connection
, "OK", 2);
477 return GDB_THREAD_PACKET_NOT_CONSUMED
;
480 static int rtos_put_gdb_reg_list(struct connection
*connection
,
481 struct rtos_reg
*reg_list
, int num_regs
)
483 size_t num_bytes
= 1; /* NUL */
484 for (int i
= 0; i
< num_regs
; ++i
)
485 num_bytes
+= DIV_ROUND_UP(reg_list
[i
].size
, 8) * 2;
487 char *hex
= malloc(num_bytes
);
490 for (int i
= 0; i
< num_regs
; ++i
) {
491 size_t count
= DIV_ROUND_UP(reg_list
[i
].size
, 8);
492 size_t n
= hexify(hex_p
, reg_list
[i
].value
, count
, num_bytes
);
497 gdb_put_packet(connection
, hex
, strlen(hex
));
503 /** Look through all registers to find this register. */
504 int rtos_get_gdb_reg(struct connection
*connection
, int reg_num
)
506 struct target
*target
= get_target_from_connection(connection
);
507 int64_t current_threadid
= target
->rtos
->current_threadid
;
508 if ((target
->rtos
) && (current_threadid
!= -1) &&
509 (current_threadid
!= 0) &&
510 ((current_threadid
!= target
->rtos
->current_thread
) ||
511 (target
->smp
))) { /* in smp several current thread are possible */
512 struct rtos_reg
*reg_list
;
515 LOG_DEBUG("getting register %d for thread 0x%" PRIx64
516 ", target->rtos->current_thread=0x%" PRIx64
,
519 target
->rtos
->current_thread
);
522 if (target
->rtos
->type
->get_thread_reg
) {
523 reg_list
= calloc(1, sizeof(*reg_list
));
525 retval
= target
->rtos
->type
->get_thread_reg(target
->rtos
,
526 current_threadid
, reg_num
, ®_list
[0]);
527 if (retval
!= ERROR_OK
) {
528 LOG_ERROR("RTOS: failed to get register %d", reg_num
);
532 retval
= target
->rtos
->type
->get_thread_reg_list(target
->rtos
,
536 if (retval
!= ERROR_OK
) {
537 LOG_ERROR("RTOS: failed to get register list");
542 for (int i
= 0; i
< num_regs
; ++i
) {
543 if (reg_list
[i
].number
== (uint32_t)reg_num
) {
544 rtos_put_gdb_reg_list(connection
, reg_list
+ i
, 1);
555 /** Return a list of general registers. */
556 int rtos_get_gdb_reg_list(struct connection
*connection
)
558 struct target
*target
= get_target_from_connection(connection
);
559 int64_t current_threadid
= target
->rtos
->current_threadid
;
560 if ((target
->rtos
) && (current_threadid
!= -1) &&
561 (current_threadid
!= 0) &&
562 ((current_threadid
!= target
->rtos
->current_thread
) ||
563 (target
->smp
))) { /* in smp several current thread are possible */
564 struct rtos_reg
*reg_list
;
567 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
568 ", target->rtos->current_thread=0x%" PRIx64
"\r\n",
570 target
->rtos
->current_thread
);
572 int retval
= target
->rtos
->type
->get_thread_reg_list(target
->rtos
,
576 if (retval
!= ERROR_OK
) {
577 LOG_ERROR("RTOS: failed to get register list");
581 rtos_put_gdb_reg_list(connection
, reg_list
, num_regs
);
589 int rtos_set_reg(struct connection
*connection
, int reg_num
,
592 struct target
*target
= get_target_from_connection(connection
);
593 int64_t current_threadid
= target
->rtos
->current_threadid
;
594 if ((target
->rtos
) &&
595 (target
->rtos
->type
->set_reg
) &&
596 (current_threadid
!= -1) &&
597 (current_threadid
!= 0)) {
598 return target
->rtos
->type
->set_reg(target
->rtos
, reg_num
, reg_value
);
603 int rtos_generic_stack_read(struct target
*target
,
604 const struct rtos_register_stacking
*stacking
,
606 struct rtos_reg
**reg_list
,
611 if (stack_ptr
== 0) {
612 LOG_ERROR("Error: null stack pointer in thread");
616 uint8_t *stack_data
= malloc(stacking
->stack_registers_size
);
617 uint32_t address
= stack_ptr
;
619 if (stacking
->stack_growth_direction
== 1)
620 address
-= stacking
->stack_registers_size
;
621 if (stacking
->read_stack
)
622 retval
= stacking
->read_stack(target
, address
, stacking
, stack_data
);
624 retval
= target_read_buffer(target
, address
, stacking
->stack_registers_size
, stack_data
);
625 if (retval
!= ERROR_OK
) {
627 LOG_ERROR("Error reading stack frame from thread");
630 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32
, address
);
633 LOG_OUTPUT("Stack Data :");
634 for (i
= 0; i
< stacking
->stack_registers_size
; i
++)
635 LOG_OUTPUT("%02X", stack_data
[i
]);
639 target_addr_t new_stack_ptr
;
640 if (stacking
->calculate_process_stack
) {
641 new_stack_ptr
= stacking
->calculate_process_stack(target
,
642 stack_data
, stacking
, stack_ptr
);
644 new_stack_ptr
= stack_ptr
- stacking
->stack_growth_direction
*
645 stacking
->stack_registers_size
;
648 *reg_list
= calloc(stacking
->num_output_registers
, sizeof(struct rtos_reg
));
649 *num_regs
= stacking
->num_output_registers
;
651 for (int i
= 0; i
< stacking
->num_output_registers
; ++i
) {
652 (*reg_list
)[i
].number
= stacking
->register_offsets
[i
].number
;
653 (*reg_list
)[i
].size
= stacking
->register_offsets
[i
].width_bits
;
655 int offset
= stacking
->register_offsets
[i
].offset
;
657 buf_cpy(&new_stack_ptr
, (*reg_list
)[i
].value
, (*reg_list
)[i
].size
);
658 else if (offset
!= -1)
659 buf_cpy(stack_data
+ offset
, (*reg_list
)[i
].value
, (*reg_list
)[i
].size
);
663 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
667 static int rtos_try_next(struct target
*target
)
669 struct rtos
*os
= target
->rtos
;
670 const struct rtos_type
**type
= rtos_types
;
675 while (*type
&& os
->type
!= *type
)
678 if (!*type
|| !*(++type
))
689 int rtos_update_threads(struct target
*target
)
691 if ((target
->rtos
) && (target
->rtos
->type
))
692 target
->rtos
->type
->update_threads(target
->rtos
);
696 void rtos_free_threadlist(struct rtos
*rtos
)
698 if (rtos
->thread_details
) {
701 for (j
= 0; j
< rtos
->thread_count
; j
++) {
702 struct thread_detail
*current_thread
= &rtos
->thread_details
[j
];
703 free(current_thread
->thread_name_str
);
704 free(current_thread
->extra_info_str
);
706 free(rtos
->thread_details
);
707 rtos
->thread_details
= NULL
;
708 rtos
->thread_count
= 0;
709 rtos
->current_threadid
= -1;
710 rtos
->current_thread
= 0;
714 int rtos_read_buffer(struct target
*target
, target_addr_t address
,
715 uint32_t size
, uint8_t *buffer
)
717 if (target
->rtos
->type
->read_buffer
)
718 return target
->rtos
->type
->read_buffer(target
->rtos
, address
, size
, buffer
);
719 return ERROR_NOT_IMPLEMENTED
;
722 int rtos_write_buffer(struct target
*target
, target_addr_t address
,
723 uint32_t size
, const uint8_t *buffer
)
725 if (target
->rtos
->type
->write_buffer
)
726 return target
->rtos
->type
->write_buffer(target
->rtos
, address
, size
, buffer
);
727 return ERROR_NOT_IMPLEMENTED
;
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)