* [PATCH v2 01/14] wpa-supplicant: Use wpa_supplicant.conf from ${S}
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 02/14] devtool: deploy-target: pass tar -m to avoid future timestamp warnings AdrianF
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Alex Kiernan, Mathieu Dubois-Briand, Richard Purdie
From: Alex Kiernan <alex.kiernan@gmail.com>
Rather than ship an example wpa_supplicant.conf from 2013, install the
upstream all options example config. This is only the version which
documents all options the minimal version installed into /etc stays the
same.
Signed-off-by: Alex Kiernan <alex.kiernan@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
---
.../wpa-supplicant/wpa_supplicant.conf | 690 ------------------
.../wpa-supplicant/wpa-supplicant_2.12.bb | 3 +-
2 files changed, 1 insertion(+), 692 deletions(-)
delete mode 100644 meta/recipes-connectivity/wpa-supplicant/wpa-supplicant/wpa_supplicant.conf
diff --git a/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant/wpa_supplicant.conf b/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant/wpa_supplicant.conf
deleted file mode 100644
index 68258f5ee2..0000000000
--- a/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant/wpa_supplicant.conf
+++ /dev/null
@@ -1,690 +0,0 @@
-##### Example wpa_supplicant configuration file ###############################
-#
-# This file describes configuration file format and lists all available option.
-# Please also take a look at simpler configuration examples in 'examples'
-# subdirectory.
-#
-# Empty lines and lines starting with # are ignored
-
-# NOTE! This file may contain password information and should probably be made
-# readable only by root user on multiuser systems.
-
-# Note: All file paths in this configuration file should use full (absolute,
-# not relative to working directory) path in order to allow working directory
-# to be changed. This can happen if wpa_supplicant is run in the background.
-
-# Whether to allow wpa_supplicant to update (overwrite) configuration
-#
-# This option can be used to allow wpa_supplicant to overwrite configuration
-# file whenever configuration is changed (e.g., new network block is added with
-# wpa_cli or wpa_gui, or a password is changed). This is required for
-# wpa_cli/wpa_gui to be able to store the configuration changes permanently.
-# Please note that overwriting configuration file will remove the comments from
-# it.
-#update_config=1
-
-# global configuration (shared by all network blocks)
-#
-# Parameters for the control interface. If this is specified, wpa_supplicant
-# will open a control interface that is available for external programs to
-# manage wpa_supplicant. The meaning of this string depends on which control
-# interface mechanism is used. For all cases, the existence of this parameter
-# in configuration is used to determine whether the control interface is
-# enabled.
-#
-# For UNIX domain sockets (default on Linux and BSD): This is a directory that
-# will be created for UNIX domain sockets for listening to requests from
-# external programs (CLI/GUI, etc.) for status information and configuration.
-# The socket file will be named based on the interface name, so multiple
-# wpa_supplicant processes can be run at the same time if more than one
-# interface is used.
-# /var/run/wpa_supplicant is the recommended directory for sockets and by
-# default, wpa_cli will use it when trying to connect with wpa_supplicant.
-#
-# Access control for the control interface can be configured by setting the
-# directory to allow only members of a group to use sockets. This way, it is
-# possible to run wpa_supplicant as root (since it needs to change network
-# configuration and open raw sockets) and still allow GUI/CLI components to be
-# run as non-root users. However, since the control interface can be used to
-# change the network configuration, this access needs to be protected in many
-# cases. By default, wpa_supplicant is configured to use gid 0 (root). If you
-# want to allow non-root users to use the control interface, add a new group
-# and change this value to match with that group. Add users that should have
-# control interface access to this group. If this variable is commented out or
-# not included in the configuration file, group will not be changed from the
-# value it got by default when the directory or socket was created.
-#
-# When configuring both the directory and group, use following format:
-# DIR=/var/run/wpa_supplicant GROUP=wheel
-# DIR=/var/run/wpa_supplicant GROUP=0
-# (group can be either group name or gid)
-#
-# For UDP connections (default on Windows): The value will be ignored. This
-# variable is just used to select that the control interface is to be created.
-# The value can be set to, e.g., udp (ctrl_interface=udp)
-#
-# For Windows Named Pipe: This value can be used to set the security descriptor
-# for controlling access to the control interface. Security descriptor can be
-# set using Security Descriptor String Format (see http://msdn.microsoft.com/
-# library/default.asp?url=/library/en-us/secauthz/security/
-# security_descriptor_string_format.asp). The descriptor string needs to be
-# prefixed with SDDL=. For example, ctrl_interface=SDDL=D: would set an empty
-# DACL (which will reject all connections). See README-Windows.txt for more
-# information about SDDL string format.
-#
-ctrl_interface=/var/run/wpa_supplicant
-
-# IEEE 802.1X/EAPOL version
-# wpa_supplicant is implemented based on IEEE Std 802.1X-2004 which defines
-# EAPOL version 2. However, there are many APs that do not handle the new
-# version number correctly (they seem to drop the frames completely). In order
-# to make wpa_supplicant interoperate with these APs, the version number is set
-# to 1 by default. This configuration value can be used to set it to the new
-# version (2).
-eapol_version=1
-
-# AP scanning/selection
-# By default, wpa_supplicant requests driver to perform AP scanning and then
-# uses the scan results to select a suitable AP. Another alternative is to
-# allow the driver to take care of AP scanning and selection and use
-# wpa_supplicant just to process EAPOL frames based on IEEE 802.11 association
-# information from the driver.
-# 1: wpa_supplicant initiates scanning and AP selection
-# 0: driver takes care of scanning, AP selection, and IEEE 802.11 association
-# parameters (e.g., WPA IE generation); this mode can also be used with
-# non-WPA drivers when using IEEE 802.1X mode; do not try to associate with
-# APs (i.e., external program needs to control association). This mode must
-# also be used when using wired Ethernet drivers.
-# 2: like 0, but associate with APs using security policy and SSID (but not
-# BSSID); this can be used, e.g., with ndiswrapper and NDIS drivers to
-# enable operation with hidden SSIDs and optimized roaming; in this mode,
-# the network blocks in the configuration file are tried one by one until
-# the driver reports successful association; each network block should have
-# explicit security policy (i.e., only one option in the lists) for
-# key_mgmt, pairwise, group, proto variables
-ap_scan=1
-
-# EAP fast re-authentication
-# By default, fast re-authentication is enabled for all EAP methods that
-# support it. This variable can be used to disable fast re-authentication.
-# Normally, there is no need to disable this.
-fast_reauth=1
-
-# OpenSSL Engine support
-# These options can be used to load OpenSSL engines.
-# The two engines that are supported currently are shown below:
-# They are both from the opensc project (http://www.opensc.org/)
-# By default no engines are loaded.
-# make the opensc engine available
-#opensc_engine_path=/usr/lib/opensc/engine_opensc.so
-# make the pkcs11 engine available
-#pkcs11_engine_path=/usr/lib/opensc/engine_pkcs11.so
-# configure the path to the pkcs11 module required by the pkcs11 engine
-#pkcs11_module_path=/usr/lib/pkcs11/opensc-pkcs11.so
-
-# Dynamic EAP methods
-# If EAP methods were built dynamically as shared object files, they need to be
-# loaded here before being used in the network blocks. By default, EAP methods
-# are included statically in the build, so these lines are not needed
-#load_dynamic_eap=/usr/lib/wpa_supplicant/eap_tls.so
-#load_dynamic_eap=/usr/lib/wpa_supplicant/eap_md5.so
-
-# Driver interface parameters
-# This field can be used to configure arbitrary driver interace parameters. The
-# format is specific to the selected driver interface. This field is not used
-# in most cases.
-#driver_param="field=value"
-
-# Maximum lifetime for PMKSA in seconds; default 43200
-#dot11RSNAConfigPMKLifetime=43200
-# Threshold for reauthentication (percentage of PMK lifetime); default 70
-#dot11RSNAConfigPMKReauthThreshold=70
-# Timeout for security association negotiation in seconds; default 60
-#dot11RSNAConfigSATimeout=60
-
-# network block
-#
-# Each network (usually AP's sharing the same SSID) is configured as a separate
-# block in this configuration file. The network blocks are in preference order
-# (the first match is used).
-#
-# network block fields:
-#
-# disabled:
-# 0 = this network can be used (default)
-# 1 = this network block is disabled (can be enabled through ctrl_iface,
-# e.g., with wpa_cli or wpa_gui)
-#
-# id_str: Network identifier string for external scripts. This value is passed
-# to external action script through wpa_cli as WPA_ID_STR environment
-# variable to make it easier to do network specific configuration.
-#
-# ssid: SSID (mandatory); either as an ASCII string with double quotation or
-# as hex string; network name
-#
-# scan_ssid:
-# 0 = do not scan this SSID with specific Probe Request frames (default)
-# 1 = scan with SSID-specific Probe Request frames (this can be used to
-# find APs that do not accept broadcast SSID or use multiple SSIDs;
-# this will add latency to scanning, so enable this only when needed)
-#
-# bssid: BSSID (optional); if set, this network block is used only when
-# associating with the AP using the configured BSSID
-#
-# priority: priority group (integer)
-# By default, all networks will get same priority group (0). If some of the
-# networks are more desirable, this field can be used to change the order in
-# which wpa_supplicant goes through the networks when selecting a BSS. The
-# priority groups will be iterated in decreasing priority (i.e., the larger the
-# priority value, the sooner the network is matched against the scan results).
-# Within each priority group, networks will be selected based on security
-# policy, signal strength, etc.
-# Please note that AP scanning with scan_ssid=1 and ap_scan=2 mode are not
-# using this priority to select the order for scanning. Instead, they try the
-# networks in the order that used in the configuration file.
-#
-# mode: IEEE 802.11 operation mode
-# 0 = infrastructure (Managed) mode, i.e., associate with an AP (default)
-# 1 = IBSS (ad-hoc, peer-to-peer)
-# Note: IBSS can only be used with key_mgmt NONE (plaintext and static WEP)
-# and key_mgmt=WPA-NONE (fixed group key TKIP/CCMP). In addition, ap_scan has
-# to be set to 2 for IBSS. WPA-None requires following network block options:
-# proto=WPA, key_mgmt=WPA-NONE, pairwise=NONE, group=TKIP (or CCMP, but not
-# both), and psk must also be set.
-#
-# proto: list of accepted protocols
-# WPA = WPA/IEEE 802.11i/D3.0
-# RSN = WPA2/IEEE 802.11i (also WPA2 can be used as an alias for RSN)
-# If not set, this defaults to: WPA RSN
-#
-# key_mgmt: list of accepted authenticated key management protocols
-# WPA-PSK = WPA pre-shared key (this requires 'psk' field)
-# WPA-EAP = WPA using EAP authentication (this can use an external
-# program, e.g., Xsupplicant, for IEEE 802.1X EAP Authentication
-# IEEE8021X = IEEE 802.1X using EAP authentication and (optionally) dynamically
-# generated WEP keys
-# NONE = WPA is not used; plaintext or static WEP could be used
-# If not set, this defaults to: WPA-PSK WPA-EAP
-#
-# auth_alg: list of allowed IEEE 802.11 authentication algorithms
-# OPEN = Open System authentication (required for WPA/WPA2)
-# SHARED = Shared Key authentication (requires static WEP keys)
-# LEAP = LEAP/Network EAP (only used with LEAP)
-# If not set, automatic selection is used (Open System with LEAP enabled if
-# LEAP is allowed as one of the EAP methods).
-#
-# pairwise: list of accepted pairwise (unicast) ciphers for WPA
-# CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0]
-# TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
-# NONE = Use only Group Keys (deprecated, should not be included if APs support
-# pairwise keys)
-# If not set, this defaults to: CCMP TKIP
-#
-# group: list of accepted group (broadcast/multicast) ciphers for WPA
-# CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0]
-# TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
-# WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
-# WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key [IEEE 802.11]
-# If not set, this defaults to: CCMP TKIP WEP104 WEP40
-#
-# psk: WPA preshared key; 256-bit pre-shared key
-# The key used in WPA-PSK mode can be entered either as 64 hex-digits, i.e.,
-# 32 bytes or as an ASCII passphrase (in which case, the real PSK will be
-# generated using the passphrase and SSID). ASCII passphrase must be between
-# 8 and 63 characters (inclusive).
-# This field is not needed, if WPA-EAP is used.
-# Note: Separate tool, wpa_passphrase, can be used to generate 256-bit keys
-# from ASCII passphrase. This process uses lot of CPU and wpa_supplicant
-# startup and reconfiguration time can be optimized by generating the PSK only
-# only when the passphrase or SSID has actually changed.
-#
-# eapol_flags: IEEE 802.1X/EAPOL options (bit field)
-# Dynamic WEP key required for non-WPA mode
-# bit0 (1): require dynamically generated unicast WEP key
-# bit1 (2): require dynamically generated broadcast WEP key
-# (3 = require both keys; default)
-# Note: When using wired authentication, eapol_flags must be set to 0 for the
-# authentication to be completed successfully.
-#
-# proactive_key_caching:
-# Enable/disable opportunistic PMKSA caching for WPA2.
-# 0 = disabled (default)
-# 1 = enabled
-#
-# wep_key0..3: Static WEP key (ASCII in double quotation, e.g. "abcde" or
-# hex without quotation, e.g., 0102030405)
-# wep_tx_keyidx: Default WEP key index (TX) (0..3)
-#
-# peerkey: Whether PeerKey negotiation for direct links (IEEE 802.11e DLS) is
-# allowed. This is only used with RSN/WPA2.
-# 0 = disabled (default)
-# 1 = enabled
-#peerkey=1
-#
-# Following fields are only used with internal EAP implementation.
-# eap: space-separated list of accepted EAP methods
-# MD5 = EAP-MD5 (unsecure and does not generate keying material ->
-# cannot be used with WPA; to be used as a Phase 2 method
-# with EAP-PEAP or EAP-TTLS)
-# MSCHAPV2 = EAP-MSCHAPv2 (cannot be used separately with WPA; to be used
-# as a Phase 2 method with EAP-PEAP or EAP-TTLS)
-# OTP = EAP-OTP (cannot be used separately with WPA; to be used
-# as a Phase 2 method with EAP-PEAP or EAP-TTLS)
-# GTC = EAP-GTC (cannot be used separately with WPA; to be used
-# as a Phase 2 method with EAP-PEAP or EAP-TTLS)
-# TLS = EAP-TLS (client and server certificate)
-# PEAP = EAP-PEAP (with tunnelled EAP authentication)
-# TTLS = EAP-TTLS (with tunnelled EAP or PAP/CHAP/MSCHAP/MSCHAPV2
-# authentication)
-# If not set, all compiled in methods are allowed.
-#
-# identity: Identity string for EAP
-# anonymous_identity: Anonymous identity string for EAP (to be used as the
-# unencrypted identity with EAP types that support different tunnelled
-# identity, e.g., EAP-TTLS)
-# password: Password string for EAP
-# ca_cert: File path to CA certificate file (PEM/DER). This file can have one
-# or more trusted CA certificates. If ca_cert and ca_path are not
-# included, server certificate will not be verified. This is insecure and
-# a trusted CA certificate should always be configured when using
-# EAP-TLS/TTLS/PEAP. Full path should be used since working directory may
-# change when wpa_supplicant is run in the background.
-# On Windows, trusted CA certificates can be loaded from the system
-# certificate store by setting this to cert_store://<name>, e.g.,
-# ca_cert="cert_store://CA" or ca_cert="cert_store://ROOT".
-# Note that when running wpa_supplicant as an application, the user
-# certificate store (My user account) is used, whereas computer store
-# (Computer account) is used when running wpasvc as a service.
-# ca_path: Directory path for CA certificate files (PEM). This path may
-# contain multiple CA certificates in OpenSSL format. Common use for this
-# is to point to system trusted CA list which is often installed into
-# directory like /etc/ssl/certs. If configured, these certificates are
-# added to the list of trusted CAs. ca_cert may also be included in that
-# case, but it is not required.
-# client_cert: File path to client certificate file (PEM/DER)
-# Full path should be used since working directory may change when
-# wpa_supplicant is run in the background.
-# Alternatively, a named configuration blob can be used by setting this
-# to blob://<blob name>.
-# private_key: File path to client private key file (PEM/DER/PFX)
-# When PKCS#12/PFX file (.p12/.pfx) is used, client_cert should be
-# commented out. Both the private key and certificate will be read from
-# the PKCS#12 file in this case. Full path should be used since working
-# directory may change when wpa_supplicant is run in the background.
-# Windows certificate store can be used by leaving client_cert out and
-# configuring private_key in one of the following formats:
-# cert://substring_to_match
-# hash://certificate_thumbprint_in_hex
-# for example: private_key="hash://63093aa9c47f56ae88334c7b65a4"
-# Note that when running wpa_supplicant as an application, the user
-# certificate store (My user account) is used, whereas computer store
-# (Computer account) is used when running wpasvc as a service.
-# Alternatively, a named configuration blob can be used by setting this
-# to blob://<blob name>.
-# private_key_passwd: Password for private key file (if left out, this will be
-# asked through control interface)
-# dh_file: File path to DH/DSA parameters file (in PEM format)
-# This is an optional configuration file for setting parameters for an
-# ephemeral DH key exchange. In most cases, the default RSA
-# authentication does not use this configuration. However, it is possible
-# setup RSA to use ephemeral DH key exchange. In addition, ciphers with
-# DSA keys always use ephemeral DH keys. This can be used to achieve
-# forward secrecy. If the file is in DSA parameters format, it will be
-# automatically converted into DH params.
-# subject_match: Substring to be matched against the subject of the
-# authentication server certificate. If this string is set, the server
-# sertificate is only accepted if it contains this string in the subject.
-# The subject string is in following format:
-# /C=US/ST=CA/L=San Francisco/CN=Test AS/emailAddress=as@example.com
-# altsubject_match: Semicolon separated string of entries to be matched against
-# the alternative subject name of the authentication server certificate.
-# If this string is set, the server sertificate is only accepted if it
-# contains one of the entries in an alternative subject name extension.
-# altSubjectName string is in following format: TYPE:VALUE
-# Example: EMAIL:server@example.com
-# Example: DNS:server.example.com;DNS:server2.example.com
-# Following types are supported: EMAIL, DNS, URI
-# phase1: Phase1 (outer authentication, i.e., TLS tunnel) parameters
-# (string with field-value pairs, e.g., "peapver=0" or
-# "peapver=1 peaplabel=1")
-# 'peapver' can be used to force which PEAP version (0 or 1) is used.
-# 'peaplabel=1' can be used to force new label, "client PEAP encryption",
-# to be used during key derivation when PEAPv1 or newer. Most existing
-# PEAPv1 implementation seem to be using the old label, "client EAP
-# encryption", and wpa_supplicant is now using that as the default value.
-# Some servers, e.g., Radiator, may require peaplabel=1 configuration to
-# interoperate with PEAPv1; see eap_testing.txt for more details.
-# 'peap_outer_success=0' can be used to terminate PEAP authentication on
-# tunneled EAP-Success. This is required with some RADIUS servers that
-# implement draft-josefsson-pppext-eap-tls-eap-05.txt (e.g.,
-# Lucent NavisRadius v4.4.0 with PEAP in "IETF Draft 5" mode)
-# include_tls_length=1 can be used to force wpa_supplicant to include
-# TLS Message Length field in all TLS messages even if they are not
-# fragmented.
-# sim_min_num_chal=3 can be used to configure EAP-SIM to require three
-# challenges (by default, it accepts 2 or 3)
-# phase2: Phase2 (inner authentication with TLS tunnel) parameters
-# (string with field-value pairs, e.g., "auth=MSCHAPV2" for EAP-PEAP or
-# "autheap=MSCHAPV2 autheap=MD5" for EAP-TTLS)
-# Following certificate/private key fields are used in inner Phase2
-# authentication when using EAP-TTLS or EAP-PEAP.
-# ca_cert2: File path to CA certificate file. This file can have one or more
-# trusted CA certificates. If ca_cert2 and ca_path2 are not included,
-# server certificate will not be verified. This is insecure and a trusted
-# CA certificate should always be configured.
-# ca_path2: Directory path for CA certificate files (PEM)
-# client_cert2: File path to client certificate file
-# private_key2: File path to client private key file
-# private_key2_passwd: Password for private key file
-# dh_file2: File path to DH/DSA parameters file (in PEM format)
-# subject_match2: Substring to be matched against the subject of the
-# authentication server certificate.
-# altsubject_match2: Substring to be matched against the alternative subject
-# name of the authentication server certificate.
-#
-# fragment_size: Maximum EAP fragment size in bytes (default 1398).
-# This value limits the fragment size for EAP methods that support
-# fragmentation (e.g., EAP-TLS and EAP-PEAP). This value should be set
-# small enough to make the EAP messages fit in MTU of the network
-# interface used for EAPOL. The default value is suitable for most
-# cases.
-#
-# EAP-PSK variables:
-# eappsk: 16-byte (128-bit, 32 hex digits) pre-shared key in hex format
-# nai: user NAI
-#
-# EAP-PAX variables:
-# eappsk: 16-byte (128-bit, 32 hex digits) pre-shared key in hex format
-#
-# EAP-SAKE variables:
-# eappsk: 32-byte (256-bit, 64 hex digits) pre-shared key in hex format
-# (this is concatenation of Root-Secret-A and Root-Secret-B)
-# nai: user NAI (PEERID)
-#
-# EAP-GPSK variables:
-# eappsk: Pre-shared key in hex format (at least 128 bits, i.e., 32 hex digits)
-# nai: user NAI (ID_Client)
-#
-# EAP-FAST variables:
-# pac_file: File path for the PAC entries. wpa_supplicant will need to be able
-# to create this file and write updates to it when PAC is being
-# provisioned or refreshed. Full path to the file should be used since
-# working directory may change when wpa_supplicant is run in the
-# background. Alternatively, a named configuration blob can be used by
-# setting this to blob://<blob name>
-# phase1: fast_provisioning=1 option enables in-line provisioning of EAP-FAST
-# credentials (PAC)
-#
-# wpa_supplicant supports number of "EAP workarounds" to work around
-# interoperability issues with incorrectly behaving authentication servers.
-# These are enabled by default because some of the issues are present in large
-# number of authentication servers. Strict EAP conformance mode can be
-# configured by disabling workarounds with eap_workaround=0.
-
-# Example blocks:
-
-# Simple case: WPA-PSK, PSK as an ASCII passphrase, allow all valid ciphers
-network={
- ssid="simple"
- psk="very secret passphrase"
- priority=5
-}
-
-# Same as previous, but request SSID-specific scanning (for APs that reject
-# broadcast SSID)
-network={
- ssid="second ssid"
- scan_ssid=1
- psk="very secret passphrase"
- priority=2
-}
-
-# Only WPA-PSK is used. Any valid cipher combination is accepted.
-network={
- ssid="example"
- proto=WPA
- key_mgmt=WPA-PSK
- pairwise=CCMP TKIP
- group=CCMP TKIP WEP104 WEP40
- psk=06b4be19da289f475aa46a33cb793029d4ab3db7a23ee92382eb0106c72ac7bb
- priority=2
-}
-
-# Only WPA-EAP is used. Both CCMP and TKIP is accepted. An AP that used WEP104
-# or WEP40 as the group cipher will not be accepted.
-network={
- ssid="example"
- proto=RSN
- key_mgmt=WPA-EAP
- pairwise=CCMP TKIP
- group=CCMP TKIP
- eap=TLS
- identity="user@example.com"
- ca_cert="/etc/cert/ca.pem"
- client_cert="/etc/cert/user.pem"
- private_key="/etc/cert/user.prv"
- private_key_passwd="password"
- priority=1
-}
-
-# EAP-PEAP/MSCHAPv2 configuration for RADIUS servers that use the new peaplabel
-# (e.g., Radiator)
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=PEAP
- identity="user@example.com"
- password="foobar"
- ca_cert="/etc/cert/ca.pem"
- phase1="peaplabel=1"
- phase2="auth=MSCHAPV2"
- priority=10
-}
-
-# EAP-TTLS/EAP-MD5-Challenge configuration with anonymous identity for the
-# unencrypted use. Real identity is sent only within an encrypted TLS tunnel.
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=TTLS
- identity="user@example.com"
- anonymous_identity="anonymous@example.com"
- password="foobar"
- ca_cert="/etc/cert/ca.pem"
- priority=2
-}
-
-# EAP-TTLS/MSCHAPv2 configuration with anonymous identity for the unencrypted
-# use. Real identity is sent only within an encrypted TLS tunnel.
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=TTLS
- identity="user@example.com"
- anonymous_identity="anonymous@example.com"
- password="foobar"
- ca_cert="/etc/cert/ca.pem"
- phase2="auth=MSCHAPV2"
-}
-
-# WPA-EAP, EAP-TTLS with different CA certificate used for outer and inner
-# authentication.
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=TTLS
- # Phase1 / outer authentication
- anonymous_identity="anonymous@example.com"
- ca_cert="/etc/cert/ca.pem"
- # Phase 2 / inner authentication
- phase2="autheap=TLS"
- ca_cert2="/etc/cert/ca2.pem"
- client_cert2="/etc/cer/user.pem"
- private_key2="/etc/cer/user.prv"
- private_key2_passwd="password"
- priority=2
-}
-
-# Both WPA-PSK and WPA-EAP is accepted. Only CCMP is accepted as pairwise and
-# group cipher.
-network={
- ssid="example"
- bssid=00:11:22:33:44:55
- proto=WPA RSN
- key_mgmt=WPA-PSK WPA-EAP
- pairwise=CCMP
- group=CCMP
- psk=06b4be19da289f475aa46a33cb793029d4ab3db7a23ee92382eb0106c72ac7bb
-}
-
-# Special characters in SSID, so use hex string. Default to WPA-PSK, WPA-EAP
-# and all valid ciphers.
-network={
- ssid=00010203
- psk=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
-}
-
-
-# IEEE 802.1X/EAPOL with dynamically generated WEP keys (i.e., no WPA) using
-# EAP-TLS for authentication and key generation; require both unicast and
-# broadcast WEP keys.
-network={
- ssid="1x-test"
- key_mgmt=IEEE8021X
- eap=TLS
- identity="user@example.com"
- ca_cert="/etc/cert/ca.pem"
- client_cert="/etc/cert/user.pem"
- private_key="/etc/cert/user.prv"
- private_key_passwd="password"
- eapol_flags=3
-}
-
-
-# LEAP with dynamic WEP keys
-network={
- ssid="leap-example"
- key_mgmt=IEEE8021X
- eap=LEAP
- identity="user"
- password="foobar"
-}
-
-# Plaintext connection (no WPA, no IEEE 802.1X)
-network={
- ssid="plaintext-test"
- key_mgmt=NONE
-}
-
-
-# Shared WEP key connection (no WPA, no IEEE 802.1X)
-network={
- ssid="static-wep-test"
- key_mgmt=NONE
- wep_key0="abcde"
- wep_key1=0102030405
- wep_key2="1234567890123"
- wep_tx_keyidx=0
- priority=5
-}
-
-
-# Shared WEP key connection (no WPA, no IEEE 802.1X) using Shared Key
-# IEEE 802.11 authentication
-network={
- ssid="static-wep-test2"
- key_mgmt=NONE
- wep_key0="abcde"
- wep_key1=0102030405
- wep_key2="1234567890123"
- wep_tx_keyidx=0
- priority=5
- auth_alg=SHARED
-}
-
-
-# IBSS/ad-hoc network with WPA-None/TKIP.
-network={
- ssid="test adhoc"
- mode=1
- proto=WPA
- key_mgmt=WPA-NONE
- pairwise=NONE
- group=TKIP
- psk="secret passphrase"
-}
-
-
-# Catch all example that allows more or less all configuration modes
-network={
- ssid="example"
- scan_ssid=1
- key_mgmt=WPA-EAP WPA-PSK IEEE8021X NONE
- pairwise=CCMP TKIP
- group=CCMP TKIP WEP104 WEP40
- psk="very secret passphrase"
- eap=TTLS PEAP TLS
- identity="user@example.com"
- password="foobar"
- ca_cert="/etc/cert/ca.pem"
- client_cert="/etc/cert/user.pem"
- private_key="/etc/cert/user.prv"
- private_key_passwd="password"
- phase1="peaplabel=0"
-}
-
-# Example of EAP-TLS with smartcard (openssl engine)
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=TLS
- proto=RSN
- pairwise=CCMP TKIP
- group=CCMP TKIP
- identity="user@example.com"
- ca_cert="/etc/cert/ca.pem"
- client_cert="/etc/cert/user.pem"
-
- engine=1
-
- # The engine configured here must be available. Look at
- # OpenSSL engine support in the global section.
- # The key available through the engine must be the private key
- # matching the client certificate configured above.
-
- # use the opensc engine
- #engine_id="opensc"
- #key_id="45"
-
- # use the pkcs11 engine
- engine_id="pkcs11"
- key_id="id_45"
-
- # Optional PIN configuration; this can be left out and PIN will be
- # asked through the control interface
- pin="1234"
-}
-
-# Example configuration showing how to use an inlined blob as a CA certificate
-# data instead of using external file
-network={
- ssid="example"
- key_mgmt=WPA-EAP
- eap=TTLS
- identity="user@example.com"
- anonymous_identity="anonymous@example.com"
- password="foobar"
- ca_cert="blob://exampleblob"
- priority=20
-}
-
-blob-base64-exampleblob={
-SGVsbG8gV29ybGQhCg==
-}
-
-
-# Wildcard match for SSID (plaintext APs only). This example select any
-# open AP regardless of its SSID.
-network={
- key_mgmt=NONE
-}
diff --git a/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant_2.12.bb b/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant_2.12.bb
index 9c2d716c03..df0585f1f3 100644
--- a/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant_2.12.bb
+++ b/meta/recipes-connectivity/wpa-supplicant/wpa-supplicant_2.12.bb
@@ -12,7 +12,6 @@ DEPENDS = "dbus libnl"
SRC_URI = "http://w1.fi/releases/wpa_supplicant-${PV}.tar.gz \
file://wpa-supplicant.sh \
- file://wpa_supplicant.conf \
file://wpa_supplicant.conf-sane \
file://99_wpa_supplicant \
"
@@ -77,7 +76,7 @@ do_install () {
oe_runmake -C wpa_supplicant DESTDIR="${D}" install
install -d ${D}${docdir}/wpa_supplicant
- install -m 644 wpa_supplicant/README ${UNPACKDIR}/wpa_supplicant.conf ${D}${docdir}/wpa_supplicant
+ install -m 644 wpa_supplicant/README ${S}/wpa_supplicant/wpa_supplicant.conf ${D}${docdir}/wpa_supplicant
install -d ${D}${sysconfdir}
install -m 600 ${UNPACKDIR}/wpa_supplicant.conf-sane ${D}${sysconfdir}/wpa_supplicant.conf
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 02/14] devtool: deploy-target: pass tar -m to avoid future timestamp warnings
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
2026-08-18 6:30 ` [PATCH v2 01/14] wpa-supplicant: Use wpa_supplicant.conf from ${S} AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 03/14] oe-selftest: devtool deploy: use QEMU target commands AdrianF
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
devtool deploy-target tars up the build output and pipes it through
ssh to the target, where it's extracted with "tar xv". If the
target's clock is behind the host, tar prints a "time stamp ... is
... in the future" warning per file, since the archived mtimes come
from the host.
Add -m (--touch) so tar sets mtimes to the current time on extraction
instead of restoring them from the archive, avoiding the warning.
Supported identically by GNU tar and BusyBox tar.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/deploy.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/scripts/lib/devtool/deploy.py b/scripts/lib/devtool/deploy.py
index 8228d9914f..35ec0b1b4d 100644
--- a/scripts/lib/devtool/deploy.py
+++ b/scripts/lib/devtool/deploy.py
@@ -108,9 +108,10 @@ def _prepare_remote_script(deploy, destdir='/', verbose=False, dryrun=False, und
lines.append('mkdir -p `dirname $manifest`')
lines.append('mkdir -p $2')
if verbose:
- lines.append(' tar xv -C $2 -f - | tee $manifest')
+ # -m avoids "time stamp is in the future" warnings if the target's clock is behind
+ lines.append(' tar xvm -C $2 -f - | tee $manifest')
else:
- lines.append(' tar xv -C $2 -f - > $manifest')
+ lines.append(' tar xvm -C $2 -f - > $manifest')
lines.append('sed -i "s!^./!$2!" $manifest')
elif not dryrun:
# Put any preserved files back
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 03/14] oe-selftest: devtool deploy: use QEMU target commands
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
2026-08-18 6:30 ` [PATCH v2 01/14] wpa-supplicant: Use wpa_supplicant.conf from ${S} AdrianF
2026-08-18 6:30 ` [PATCH v2 02/14] devtool: deploy-target: pass tar -m to avoid future timestamp warnings AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 04/14] oe-selftest: devtool ide-sdk: Add new test classes for ide-sdk tests AdrianF
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Use the QEMU target API for target-side verification in devtool deploy
selftests instead of invoking SSH directly. Copy the generated file list
through the target API before comparing deployed file metadata.
There are more such instances in the devtool ide-sdk selftests that
could be converted to use the QEMU target API, but this will be done by
follow-up patches with more changes than just this refactoring.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index df0910aaa6..b0c7835134 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2070,8 +2070,8 @@ class DevtoolDeployTargetTests(DevtoolBase):
self.logger.debug(deploy_cmd)
result = runCmd(deploy_cmd)
# Run a test command to see if it was installed properly
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
+ status, _ = qemu.run(testcommand)
+ self.assertEqual(status, 0)
# Check if it deployed all of the files with the right ownership/perms
# First look on the host - need to do this under pseudo to get the correct ownership/perms
bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD', 'PATH'], testrecipe)
@@ -2090,15 +2090,21 @@ class DevtoolDeployTargetTests(DevtoolBase):
for line in filelist1:
splitline = line.split()
f.write(splitline[-1] + '\n')
- result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip))
- filelist2 = self._process_ls_output(result.output)
+ remotefilelist = '/tmp/%s' % os.path.basename(tmpfilelist)
+ status, _ = qemu.copy_to(tmpfilelist, remotefilelist)
+ self.assertEqual(status, 0)
+ status, output = qemu.run(
+ 'xargs ls -l < %s; status=$?; rm -f %s; exit $status' % (
+ remotefilelist, remotefilelist))
+ self.assertEqual(status, 0)
+ filelist2 = self._process_ls_output(output)
filelist1.sort(key=lambda item: item.split()[-1])
filelist2.sort(key=lambda item: item.split()[-1])
self.assertEqual(filelist1, filelist2)
# Test undeploy-target
result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip))
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
- self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
+ status, _ = qemu.run(testcommand)
+ self.assertNotEqual(status, 0, 'undeploy-target did not remove command as it should have')
class DevtoolBuildImageTests(DevtoolBase):
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 04/14] oe-selftest: devtool ide-sdk: Add new test classes for ide-sdk tests
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (2 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 03/14] oe-selftest: devtool deploy: use QEMU target commands AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 05/14] devtool: ide-sdk: fix meson IntelliSense for gcc toolchain AdrianF
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
This allows to run the ide-sdk tests with different toolchains
(gcc, clang) and build systems (cmake, meson) in parallel.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 211 +++++++++++++-----------
1 file changed, 111 insertions(+), 100 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index b0c7835134..051be713b0 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3063,6 +3063,105 @@ class DevtoolIdeSdkTests(DevtoolBase):
exe_break_line=63 + LINE_SHIFT, exe_list_line=55 + LINE_SHIFT,
hpp_break_line=21 + LINE_SHIFT, lib_break_line=31 + LINE_SHIFT)
+ def _verify_cmake_preset(self, tempdir):
+ """Verify the generated cmake preset works as expected
+
+ Check if compiling works
+ Check if unit tests can be executed in qemu (not qemu-system)
+ """
+ with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j:
+ cmake_preset_d = json.load(cmake_preset_j)
+ config_presets = cmake_preset_d["configurePresets"]
+ self.assertEqual(len(config_presets), 1)
+ cmake_exe = config_presets[0]["cmakeExecutable"]
+ preset_name = config_presets[0]["name"]
+ compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name)
+
+ # Verify the wrapper for cmake native is available
+ self.assertExists(cmake_exe)
+
+ # Verify the cmake preset generated by devtool ide-sdk is available
+ result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn(preset_name, result.output)
+
+ # Verify cmake re-uses the o files compiled by bitbake
+ result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("ninja: no work to do.", result.output)
+
+ # Verify the unit tests work (in Qemu user mode)
+ result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("100% tests passed", result.output)
+
+ # Verify re-building and testing works again
+ result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("Cleaning", result.output)
+ result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("Building", result.output)
+ self.assertIn("Linking", result.output)
+ result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("Running tests...", result.output)
+ self.assertIn("100% tests passed", result.output)
+
+ return compile_cmd
+
+ def _verify_meson_build(self, tempdir, recipe_name):
+ """Verify meson works as expected
+
+ Check if compiling works
+ Check if unit tests can be executed in qemu (not qemu-system)
+ """
+ meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson")
+ self.assertExists(meson_exe)
+ build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0")
+ compile_cmd = '%s compile -C %s' % (meson_exe, build_dir)
+
+ # Verify meson re-uses the o files compiled by bitbake
+ result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("ninja: no work to do.", result.output)
+
+ # Verify the unit tests work (in Qemu user mode)
+ result = runCmd('%s test -C %s' % (meson_exe, build_dir),
+ cwd=tempdir, output_log=self._cmd_logger)
+ self.assertEqual(result.status, 0)
+ self.assertRegex(result.output, r"Fail:\s+0")
+
+ # Verify re-building and testing works again
+ result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir),
+ cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("Cleaning...", result.output)
+ result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
+ self.assertIn("Linking target", result.output)
+ result = runCmd('%s test -C %s' % (meson_exe, build_dir),
+ cwd=tempdir, output_log=self._cmd_logger)
+ self.assertEqual(result.status, 0)
+ self.assertRegex(result.output, r"Fail:\s+0")
+
+ return compile_cmd
+
+ def _verify_service_running(self, qemu, service_name):
+ """Helper to verify a service is running in Qemu"""
+ # Use anchored regex (^name$) instead of pgrep -x because the target
+ # may have busybox pgrep which does not support the -x flag.
+ status, output = qemu.run("pgrep '^%s$'" % service_name)
+ self.assertEqual(status, 0, msg="%s service not running: %s" %
+ (service_name, output))
+ self.assertTrue(output.strip().isdigit(),
+ f"pgrep output should be a PID integer, got: {output.strip()}")
+
+ def _verify_conf_file(self, qemu, conf_file, owner, group):
+ """Helper to verify a configuration file is owned by the proper user and group"""
+ stat_cmd = "stat -c '%%U %%G' %s" % conf_file
+ status, output = qemu.run(stat_cmd)
+ self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output))
+ actual_owner, actual_group = output.strip().split()
+ self.assertEqual(actual_owner, owner,
+ msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner))
+ self.assertEqual(actual_group, group,
+ msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group))
+
+
+class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
+
def _gdb_cross(self):
"""Verify gdb-cross is provided by devtool ide-sdk"""
target_arch = self.td["TARGET_ARCH"]
@@ -3207,102 +3306,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertEqual(r.status, 0)
self.assertNotIn("gdbserver ", r.output)
- def _verify_cmake_preset(self, tempdir):
- """Verify the generated cmake preset works as expected
-
- Check if compiling works
- Check if unit tests can be executed in qemu (not qemu-system)
- """
- with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j:
- cmake_preset_d = json.load(cmake_preset_j)
- config_presets = cmake_preset_d["configurePresets"]
- self.assertEqual(len(config_presets), 1)
- cmake_exe = config_presets[0]["cmakeExecutable"]
- preset_name = config_presets[0]["name"]
- compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name)
-
- # Verify the wrapper for cmake native is available
- self.assertExists(cmake_exe)
-
- # Verify the cmake preset generated by devtool ide-sdk is available
- result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn(preset_name, result.output)
-
- # Verify cmake re-uses the o files compiled by bitbake
- result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("ninja: no work to do.", result.output)
-
- # Verify the unit tests work (in Qemu user mode)
- result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("100% tests passed", result.output)
-
- # Verify re-building and testing works again
- result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("Cleaning", result.output)
- result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("Building", result.output)
- self.assertIn("Linking", result.output)
- result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("Running tests...", result.output)
- self.assertIn("100% tests passed", result.output)
-
- return compile_cmd
-
- def _verify_meson_build(self, tempdir, recipe_name):
- """Verify meson works as expected
-
- Check if compiling works
- Check if unit tests can be executed in qemu (not qemu-system)
- """
- meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson")
- self.assertExists(meson_exe)
- build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0")
- compile_cmd = '%s compile -C %s' % (meson_exe, build_dir)
-
- # Verify meson re-uses the o files compiled by bitbake
- result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("ninja: no work to do.", result.output)
-
- # Verify the unit tests work (in Qemu user mode)
- result = runCmd('%s test -C %s' % (meson_exe, build_dir),
- cwd=tempdir, output_log=self._cmd_logger)
- self.assertEqual(result.status, 0)
- self.assertRegex(result.output, r"Fail:\s+0")
-
- # Verify re-building and testing works again
- result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir),
- cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("Cleaning...", result.output)
- result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger)
- self.assertIn("Linking target", result.output)
- result = runCmd('%s test -C %s' % (meson_exe, build_dir),
- cwd=tempdir, output_log=self._cmd_logger)
- self.assertEqual(result.status, 0)
- self.assertRegex(result.output, r"Fail:\s+0")
-
- return compile_cmd
-
- def _verify_service_running(self, qemu, service_name):
- """Helper to verify a service is running in Qemu"""
- # Use anchored regex (^name$) instead of pgrep -x because the target
- # may have busybox pgrep which does not support the -x flag.
- status, output = qemu.run("pgrep '^%s$'" % service_name)
- self.assertEqual(status, 0, msg="%s service not running: %s" %
- (service_name, output))
- self.assertTrue(output.strip().isdigit(),
- f"pgrep output should be a PID integer, got: {output.strip()}")
-
- def _verify_conf_file(self, qemu, conf_file, owner, group):
- """Helper to verify a configuration file is owned by the proper user and group"""
- stat_cmd = "stat -c '%%U %%G' %s" % conf_file
- status, output = qemu.run(stat_cmd)
- self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output))
- actual_owner, actual_group = output.strip().split()
- self.assertEqual(actual_owner, owner,
- msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner))
- self.assertEqual(actual_group, group,
- msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group))
-
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_qemu(self):
"""Start qemu-system and run tests for multiple recipes. ide=none is used."""
@@ -3701,6 +3704,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
# Verify deployment and remote debugging works
self._verify_launch_json_debugging(tempdir, qemu, example_exe)
+class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests):
+
@OETestTag("runqemu")
def test_devtool_ide_sdk_code_kernel_module(self):
"""Verify a kernel module recipe works with ide=code mode
@@ -3916,6 +3921,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertIn(MAGIC_STRING_NEW, output,
'New magic string not found in sysfs after rebuild: %s' % output)
+class DevtoolIdeSdkSharedTests(DevtoolIdeSdkTests):
+
def test_devtool_ide_sdk_shared_sysroots(self):
"""Verify the shared sysroot SDK"""
@@ -3977,6 +3984,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src, output_log=self._cmd_logger)
runCmdEnv('meson compile', cwd=tempdir_meson, output_log=self._cmd_logger)
+class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests):
+
def _verify_launch_json_lldb(self, tempdir):
"""Verify the launch.json file contains valid CodeLLDB (type: lldb) configurations."""
launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json')
@@ -4320,7 +4329,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
self._lldb_debug_cpp_example_check(r.output, magic_string)
@OETestTag("runqemu")
- def test_devtool_ide_sdk_code_cmake_clang(self):
+ def test_devtool_ide_sdk_code_cmake(self):
"""Verify a cmake recipe built with clang works with ide=code (CodeLLDB debugging).
This test uses the cmake-example-clang recipe which is a cmake-example variant
@@ -4375,7 +4384,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
tempdir, qemu, magic_string))
@OETestTag("runqemu")
- def test_devtool_ide_sdk_code_meson_clang(self):
+ def test_devtool_ide_sdk_code_meson(self):
"""Verify a meson recipe built with clang works with ide=code (CodeLLDB debugging).
This is the meson/ninja counterpart of test_devtool_ide_sdk_code_cmake_clang.
@@ -4498,7 +4507,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertNotIn('lldb-server', r.output)
@OETestTag("runqemu")
- def test_devtool_ide_sdk_none_cmake_clang(self):
+ def test_devtool_ide_sdk_none_cmake(self):
"""Verify ide=none generates correct LLDB scripts for a clang cmake recipe.
Uses cmake-example-clang (TOOLCHAIN = "clang") which is built with the
@@ -4561,7 +4570,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
tempdir, qemu, recipe_name, example_exe, magic_string))
@OETestTag("runqemu")
- def test_devtool_ide_sdk_none_meson_clang(self):
+ def test_devtool_ide_sdk_none_meson(self):
"""Verify ide=none generates correct LLDB scripts for a clang meson recipe.
This is the meson/ninja counterpart of test_devtool_ide_sdk_none_cmake_clang.
@@ -4629,6 +4638,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
lambda magic_string: self._lldb_none_debugging_multi(
tempdir, qemu, recipe_name, example_exe, magic_string))
+class DevtoolIdeSdkMiscTests(DevtoolIdeSdkTests):
+
def test_devtool_ide_sdk_plugins(self):
"""Test that devtool ide-sdk can use plugins from other layers."""
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 05/14] devtool: ide-sdk: fix meson IntelliSense for gcc toolchain
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (3 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 04/14] oe-selftest: devtool ide-sdk: Add new test classes for ide-sdk tests AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 06/14] ccp-example: fix PID file handling AdrianF
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
The configurationProvider = "mesonbuild.mesonbuild" approach in
c_cpp_properties.json worked for meson recipes built with clang (the
__gen_meson_absolute_cross_file workaround absolutized the clang
binary path in an extra cross-file so the mesonbuild extension could
find it), but failed for the gcc toolchain: the extension could not
resolve the bare compiler name in the meson.cross file and silently
fell back to the host compiler for IntelliSense. The sysroot was also
absent, so built-in headers were resolved against the host system.
Replace the extension-based approach with explicit settings that work
for both gcc and clang toolchains:
- compilerPath: absolute path to the cross CXX binary in
staging_bindir_toolchain, so cpptools finds the right compiler
- compilerArgs: ["--sysroot=<recipe_sysroot>"], so built-in
includes are resolved against the target sysroot headers
- compileCommands: path to compile_commands.json in the meson build
directory B, which meson/ninja generates automatically with the
exact per-file flags (include paths, defines, -march= etc.)
- intelliSenseMode: derived from TARGET_ARCH and TOOLCHAIN
(linux-gcc-arm64 / linux-clang-x64, etc.)
With compileCommands pointing at the real compilation database, the
mesonbuild extension is no longer needed for IntelliSense, so the
__gen_meson_absolute_cross_file workaround is also removed.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/ide_code.py | 66 +++++++--------------
1 file changed, 23 insertions(+), 43 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
index 9faba3f2d1..d190858d44 100644
--- a/scripts/lib/devtool/ide_plugins/ide_code.py
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -147,45 +147,6 @@ class IdeVSCode(IdeBase):
def dot_code_dir(self, modified_recipe):
return os.path.join(modified_recipe.srctree, '.vscode')
- def __gen_meson_absolute_cross_file(self, modified_recipe):
- """Generate an extra cross file overriding c/cpp binaries with absolute paths.
-
- The recipe's generated meson.cross references the toolchain binaries
- (e.g. "aarch64-poky-linux-clang++") by bare name, relying on PATH
- being set up by the meson wrapper script at build time. Meson stores
- that command exactly as configured (it never resolves it to an
- absolute path itself), so it ends up unresolved in
- compile_commands.json and meson-info/intro-compilers.json. cpptools
- (via the mesonbuild extension) resolves compilerPath using its own
- process PATH, which does not include the toolchain directory, and
- silently falls back to a host compiler for IntelliSense.
-
- Real builds are unaffected since the wrapper script sets up PATH, so
- the recipe's meson.cross is left untouched. Instead, an extra
- --cross-file is layered on top with only the c/cpp [binaries]
- entries absolutized, reusing the exact same flags as CC/CXX to avoid
- any behavior drift. Meson merges multiple machine files, with later
- files overriding matching keys from earlier ones.
- """
- def absolutize(cmd):
- args = cmd.split()
- args[0] = os.path.join(
- modified_recipe.staging_bindir_toolchain, args[0])
- return repr(args)
-
- lines = ["[binaries]"]
- if modified_recipe.cc:
- lines.append("c = %s" % absolutize(modified_recipe.cc))
- if modified_recipe.cxx:
- lines.append("cpp = %s" % absolutize(modified_recipe.cxx))
-
- os.makedirs(modified_recipe.ide_sdk_dir, exist_ok=True)
- cross_file = os.path.join(
- modified_recipe.ide_sdk_dir, 'meson-absolute-toolchain.cross')
- with open(cross_file, 'w') as f:
- f.write(os.linesep.join(lines) + os.linesep)
- return ['--cross-file', cross_file]
-
def __vscode_settings_meson(self, settings_dict, modified_recipe):
if modified_recipe.build_tool is not BuildTool.MESON:
return
@@ -193,11 +154,16 @@ class IdeVSCode(IdeBase):
confopts = modified_recipe.mesonopts.split()
confopts += modified_recipe.meson_cross_file.split()
- if modified_recipe.meson_cross_file:
- confopts += self.__gen_meson_absolute_cross_file(modified_recipe)
confopts += modified_recipe.extra_oemeson.split()
settings_dict["mesonbuild.configureOptions"] = confopts
settings_dict["mesonbuild.buildFolder"] = modified_recipe.b
+ # Prevent the extension from writing C_Cpp.default.configurationProvider
+ # for itself once meson-info.json appears, which would take precedence
+ # over the compileCommands set in c_cpp_properties.json.
+ settings_dict["mesonbuild.modifySettings"] = False
+ # Clear any such value a previous run (or extension version without
+ # modifySettings support) already wrote to this workspace's settings.
+ settings_dict["C_Cpp.default.configurationProvider"] = ""
def __vscode_settings_cmake(self, settings_dict, modified_recipe):
"""Add cmake specific settings to settings.json.
@@ -328,8 +294,22 @@ class IdeVSCode(IdeBase):
if modified_recipe.build_tool is BuildTool.CMAKE:
properties_dict["configurationProvider"] = "ms-vscode.cmake-tools"
elif modified_recipe.build_tool is BuildTool.MESON:
- properties_dict["configurationProvider"] = "mesonbuild.mesonbuild"
- properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0])
+ # configurationProvider = "mesonbuild.mesonbuild" does not work because
+ # the cross-compiler is not in PATH and omits the sysroot by default.
+ compiler_type = 'clang' if modified_recipe.toolchain == 'clang' else 'gcc'
+ arch_map = {
+ 'aarch64': 'arm64', 'x86_64': 'x64', 'arm': 'arm',
+ 'i686': 'x86', 'i586': 'x86', 'riscv64': 'x64', 'riscv32': 'x86',
+ }
+ intelli_arch = arch_map.get(modified_recipe.target_arch, 'x64')
+ properties_dict["compilerPath"] = os.path.join(
+ modified_recipe.staging_bindir_toolchain,
+ modified_recipe.cxx.split()[0])
+ properties_dict["compilerArgs"] = [
+ "--sysroot=" + modified_recipe.recipe_sysroot]
+ properties_dict["compileCommands"] = os.path.join(
+ modified_recipe.b, 'compile_commands.json')
+ properties_dict["intelliSenseMode"] = "linux-%s-%s" % (compiler_type, intelli_arch)
elif modified_recipe.build_tool is BuildTool.KERNEL_MODULE:
# Using e.g. configurationProvider = "ms-vscode.makefile-tools" was not successful
properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.kernel_cc.split()[0])
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 06/14] ccp-example: fix PID file handling
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (4 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 05/14] devtool: ide-sdk: fix meson IntelliSense for gcc toolchain AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 07/14] devtool: ide-sdk: default to all modified recipes AdrianF
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Remove the PID file if the process is not running anymore.
This can happen when a debugger kills the process it debugs.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta-selftest/recipes-test/cpp/files/cpp-example.init | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.init b/meta-selftest/recipes-test/cpp/files/cpp-example.init
index c154fd1126..30b8486eeb 100644
--- a/meta-selftest/recipes-test/cpp/files/cpp-example.init
+++ b/meta-selftest/recipes-test/cpp/files/cpp-example.init
@@ -14,6 +14,11 @@ PIDFILE="/var/run/$DAEMON.pid"
LOCK_FILE="/var/lock/subsys/$DAEMON"
start() {
+ # A debugger which kills the process it debugs leaves the PID file behind
+ if [ -f $PIDFILE ] && ! kill -0 $(cat $PIDFILE) 2>/dev/null; then
+ rm -f $PIDFILE $LOCK_FILE
+ fi
+
if [ -f $PIDFILE ]; then
echo "$DAEMON is already running."
return 1
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 07/14] devtool: ide-sdk: default to all modified recipes
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (5 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 06/14] ccp-example: fix PID file handling AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 08/14] devtool: ide-sdk: let gdbserver reuse the shared debug server wait helper AdrianF
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Simplify ide-sdk usage in modified mode: keep the image recipe explicit,
but when no modified recipe is passed, take all modified recipes from
<workspace>/appends/*.bbappend.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_sdk.py | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py
index 42b6e381c5..ab2bba5d39 100755
--- a/scripts/lib/devtool/ide_sdk.py
+++ b/scripts/lib/devtool/ide_sdk.py
@@ -1258,6 +1258,15 @@ def ide_setup(args, config, basepath, workspace):
recipes_modified_names))
invalid_params = True
if args.mode == DevtoolIdeMode.modified:
+ if not recipes_modified_names:
+ appends_dir = os.path.join(config.workspace_path, 'appends')
+ recipes_modified_names = sorted(
+ os.path.splitext(os.path.basename(path))[0]
+ for path in glob.glob(os.path.join(appends_dir, '*.bbappend')))
+ if recipes_modified_names:
+ logger.info(
+ "No modified recipes specified, using workspace bbappends from %s: %s",
+ appends_dir, ', '.join(recipes_modified_names))
if len(recipes_other_names):
logger.error("Only in shared sysroots mode not modified recipes %s can be handled." % str(
recipes_other_names))
@@ -1266,6 +1275,11 @@ def ide_setup(args, config, basepath, workspace):
logger.error(
"One image recipe is required as the rootfs for the remote development.")
invalid_params = True
+ if not recipes_modified_names:
+ logger.error(
+ "At least one modified recipe is required or must be guessable from %s." %
+ os.path.join(config.workspace_path, 'appends'))
+ invalid_params = True
for modified_recipe_name in recipes_modified_names:
if modified_recipe_name.startswith('nativesdk-') or modified_recipe_name.endswith('-native'):
logger.error(
@@ -1422,7 +1436,9 @@ def register_commands(subparsers, context):
help='Setup the SDK and configure the IDE')
parser_ide_sdk.add_argument(
'recipenames', nargs='+', help='Generate an IDE configuration suitable to work on the given recipes.\n'
- 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.')
+ 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.\n'
+ 'In modified mode at least the image recipe is required; if no modified recipe is passed, '
+ 'all modified recipes are taken from <workspace>/appends/*.bbappend.')
parser_ide_sdk.add_argument(
'-m', '--mode', type=DevtoolIdeMode, default=DevtoolIdeMode.modified,
help='Different SDK types are supported:\n'
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 08/14] devtool: ide-sdk: let gdbserver reuse the shared debug server wait helper
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (6 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 07/14] devtool: ide-sdk: default to all modified recipes AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 09/14] oe-selftest: devtool ide-sdk: support $ in gdbserver task command AdrianF
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
GdbCrossConfig open coded its own copy of the loop which waits for the
debug server to listen on its port, while LldbServerConfig already used
the _target_wait_for_tcp_port_cmd() helper of the common base class.
Use the helper for gdbserver too, which requires capturing the gdbserver
pid in a shell variable so the helper can refer to it, and defining
DEBUG_SERVER_NAME for GdbCrossConfig.
Beside removing the duplication this also gives gdbserver the two things
the helper already did for lldb-server: the server is killed instead of
left behind when it never starts listening, and the reason is printed
rather than exiting silently.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/__init__.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index 63e9682e73..5211df5806 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -139,6 +139,7 @@ class GdbCrossConfig(DebuggerCrossConfig):
gdbinit / gdb wrapper scripts used by ide=none as well as the
target-side tmp/pid/log paths consumed by the gdbserver start command.
"""
+ DEBUG_SERVER_NAME = "gdbserver"
def __init__(self, image_recipe, modified_recipe, binary,
default_mode=DebuggerServerModes.MULTI):
@@ -193,14 +194,13 @@ class GdbCrossConfig(DebuggerCrossConfig):
else:
raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path)
elif server_mode == DebuggerServerModes.MULTI:
- hex_port = "%04X" % self.debug_server_port
- gdbserver_cmd_start = "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null && exit 0; " % hex_port
+ gdbserver_cmd_start = self._target_tcp_port_check_cmd() + " && exit 0; "
gdbserver_cmd_start += "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode)
- gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & " % (
- self.debugger_cross.debug_server_path, self.debug_server_port, self._gdbserver_log_file(server_mode))
- gdbserver_cmd_start += "echo \\$! > %s; " % self._gdbserver_pid_file(server_mode)
- gdbserver_cmd_start += "_w=0; while ! grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null; " % hex_port
- gdbserver_cmd_start += "do _w=\\$((_w+1)); [ \\$_w -lt 100 ] || exit 1; sleep 0.1; done;"
+ gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & _gdbserver_pid=\\$!; " % (
+ self.debugger_cross.debug_server_path, self.debug_server_port,
+ self._gdbserver_log_file(server_mode))
+ gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode)
+ gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd("gdbserver_pid")
else:
raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode)
return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\""
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 09/14] oe-selftest: devtool ide-sdk: support $ in gdbserver task command
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (7 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 08/14] devtool: ide-sdk: let gdbserver reuse the shared debug server wait helper AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 10/14] devtool: ide-sdk: attach via extended-remote AdrianF
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
This is a preparation commit for the next commit that will add a new a
variable to the tasks.json file to support gdbserver commands.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 051be713b0..0791422461 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3576,10 +3576,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
# Start gdbserver on target using the task command (keep the ssh connection open while debugging)
ssh_gdbserver_cmd = [task_command] + task_args
- # Fix shell command escaping - remove extra quotes from the last argument
- # The task_args likely contains a quoted shell command that needs to be unquoted
+ # The tasks.json argument is formatted for an intermediate shell. Strip
+ # its quotes and restore dollar expansions before passing it directly to
+ # SSH via subprocess.
if len(ssh_gdbserver_cmd) > 0 and ssh_gdbserver_cmd[-1].startswith('"') and ssh_gdbserver_cmd[-1].endswith('"'):
- ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1] # Remove surrounding quotes
+ ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1].replace('\\$', '$') # Remove surrounding quotes
self.logger.debug(f"Starting gdbserver with command: {' '.join(ssh_gdbserver_cmd)}")
with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger):
# Give gdbserver a moment to start
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 10/14] devtool: ide-sdk: attach via extended-remote
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (8 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 09/14] oe-selftest: devtool ide-sdk: support $ in gdbserver task command AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 11/14] oe-selftest: devtool ide-sdk: wait for gdbserver readiness AdrianF
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
The old ATTACH mode started gdbserver with "--attach :<port>
$(pidof <binary>)" and used a plain "request": "launch" config. This
is the classic cpptools attach setup, which is fragile and has several
long-standing, unfixed bugs, e.g.
https://github.com/microsoft/vscode-cpptools/issues/4166.
{
"name": "Attach with GDB",
"type": "cppdbg",
"request": "launch",
"program": "<program>",
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"miDebuggerServerAddress": "<target>:<port>",
"postDebugTask": "kill_gdbserver_..."
}
cppdbg also supports attaching over the extended-remote protocol:
{
"name": "Attach with GDB",
"type": "cppdbg",
"request": "attach",
"program": "<program>",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"miDebuggerServerAddress": "<target>:<port>",
"useExtendedRemote": true
}
Switch to this instead: merge ATTACH into the same persistent
"gdbserver --multi" server already used for MULTI mode, since both are
now extended-remote sessions that only differ on the client side, and
stop it by PID instead of pgrep/killall. ONCE mode gets the same
readiness synchronization MULTI already had, since gdbserver now
always writes a PID file that the stop commands wait on.
Allocate a separate debug server port for each mode. This prevents the
persistent attach endpoint from colliding with a once session for the
same binary.
Until https://github.com/microsoft/vscode-cpptools/pull/14684 is
merged, this comes with one downside (which was the blocker for using
extended-remote attach mode until now): the user is prompted to pick the
process to attach to, instead of it being selected automatically. But
this is a temporary limitation, and the new setup is more robust and
fixes several long-standing issues with the old ATTACH mode.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 23 ++++-
scripts/lib/devtool/ide_plugins/__init__.py | 103 +++++++++++++-------
scripts/lib/devtool/ide_plugins/ide_code.py | 94 ++++++++++--------
scripts/lib/devtool/ide_plugins/ide_none.py | 44 +++++----
4 files changed, 166 insertions(+), 98 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 0791422461..35abc6a48f 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3397,21 +3397,31 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
# Track configurations found
once_configs = []
attach_configs = []
+ server_addresses = []
for config in configurations:
# Verify required fields exist
- required_fields = ["name", "type", "request", "program", "cwd", "MIMode",
+ required_fields = ["name", "type", "request", "program", "MIMode",
"miDebuggerPath", "miDebuggerServerAddress"]
for field in required_fields:
self.assertIn(field, config, f"Configuration '{config.get('name', 'Unknown')}' missing required field: {field}")
# Verify common configuration values
self.assertEqual(config["type"], "cppdbg", f"Configuration '{config['name']}' should use cppdbg type")
- self.assertEqual(config["request"], "launch", f"Configuration '{config['name']}' should be launch type")
- self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd")
self.assertEqual(config["MIMode"], "gdb", f"Configuration '{config['name']}' should use gdb MIMode")
- self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console")
- self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry")
+
+ if config["request"] == "launch":
+ self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd")
+ self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console")
+ self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry")
+ elif config["request"] == "attach":
+ # Attaching to a process running on the target requires the
+ # extended-remote protocol. Stopping the session then detaches
+ # from the process instead of killing it.
+ self.assertTrue(config.get("useExtendedRemote"), f"Configuration '{config['name']}' should use useExtendedRemote")
+ self.assertNotIn("cwd", config, f"Configuration '{config['name']}' should not set cwd in attach mode")
+ else:
+ self.fail(f"Configuration '{config['name']}' has unexpected request type: {config['request']}")
# Verify program path is absolute and exists conceptually
program = config["program"]
@@ -3426,6 +3436,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
# Verify server address format
server_addr = config["miDebuggerServerAddress"]
self.assertRegex(server_addr, r"^\d+\.\d+\.\d+\.\d+:\d+$", f"Configuration '{config['name']}' server address should be IP:PORT format: {server_addr}")
+ server_addresses.append(server_addr)
# Verify additional SO lib search path exists and contains debug paths
so_paths = config.get("additionalSOLibSearchPath", [])
@@ -3466,6 +3477,8 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
# Verify we have expected configuration types
self.assertEqual(len(once_configs), 2, f"Should have two once configuration, found: {once_configs}")
self.assertEqual(len(attach_configs), 1, f"Should have one attach configuration, found: {attach_configs}")
+ self.assertEqual(len(server_addresses), len(set(server_addresses)),
+ "Each debug configuration should use a distinct server address")
def _verify_launch_json_debugging(self, tempdir, qemu, example_exe):
"""Verify remote debugging and deployment works using launch.json configurations
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index 5211df5806..0209155dce 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -59,8 +59,11 @@ class DebuggerCrossConfig:
self.binary = binary
self.default_mode = default_mode
self.binary_pretty = self.binary.binary_path.replace(os.sep, '-').lstrip('-')
- self.debug_server_port = DebuggerCrossConfig._port_next
- DebuggerCrossConfig._port_next += 1
+ self.debug_server_ports = {}
+ for mode in self.server_modes():
+ self.debug_server_ports[mode] = DebuggerCrossConfig._port_next
+ DebuggerCrossConfig._port_next += 1
+ self.debug_server_port = self.debug_server_ports[self.default_mode]
self.id_pretty = "%d_%s" % (self.debug_server_port, self.binary_pretty)
if self.id_pretty in DebuggerCrossConfig._configs:
@@ -68,8 +71,14 @@ class DebuggerCrossConfig:
"debugger config for binary %s is already generated" % binary)
DebuggerCrossConfig._configs[self.id_pretty] = self
+ def port(self, mode=None):
+ """Return the debug server port allocated for a server mode."""
+ if mode is None:
+ mode = self.default_mode
+ return self.debug_server_ports[mode]
+
def id_pretty_mode(self, mode):
- return "%s_%s" % (self.id_pretty, mode.name.lower())
+ return "%d_%s_%s" % (self.port(mode), self.binary_pretty, mode.name.lower())
# Host-side script paths
@property
@@ -100,15 +109,22 @@ class DebuggerCrossConfig:
# Re-tries in 0.1s Example: 300 * 0.1s, i.e. ~30s.
TARGET_START_RETRIES = 300
- def _target_tcp_port_check_cmd(self):
- hex_port = "%04X" % self.debug_server_port
+ def _target_tcp_port_check_cmd(self, mode=None):
+ hex_port = "%04X" % self.port(mode)
return "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null" % hex_port
- def _target_wait_for_tcp_port_cmd(self, pid_var=None, log_file=None):
+ def get_debug_server_ready_marker(self, port):
+ return "%s ready on port %s" % (self.DEBUG_SERVER_NAME, port)
+
+ def get_debug_server_ready_marker_pattern(self):
+ return "^%s$" % self.get_debug_server_ready_marker("[0-9]+")
+
+ def _target_wait_for_tcp_port_cmd(self, pid_var=None, log_file=None, mode=None):
"""Shell fragment waiting until the debug server listens on its port.
The server log is dumped to stderr when giving up.
"""
+ port = self.port(mode)
dump_log = "cat %s >&2; " % log_file if log_file else ""
cleanup = ""
if pid_var:
@@ -117,8 +133,15 @@ class DebuggerCrossConfig:
"_w=0; while ! %s; do _w=\\$((_w+1)); [ \\$_w -lt %d ] || { "
"%secho %s did not start on port %s after \\$_w retries >&2; %sexit 1; }; "
"sleep 0.1; done;"
- % (self._target_tcp_port_check_cmd(), self.TARGET_START_RETRIES,
- cleanup, self.DEBUG_SERVER_NAME, self.debug_server_port, dump_log))
+ % (self._target_tcp_port_check_cmd(mode), self.TARGET_START_RETRIES,
+ cleanup, self.DEBUG_SERVER_NAME, port, dump_log))
+
+ def _target_wait_for_process_exit_cmd(self, pid_var):
+ return (
+ "_w=0; while kill -0 \\$_%s 2>/dev/null; do _w=\\$((_w+1)); "
+ "[ \\$_w -lt 100 ] || { echo %s did not stop >&2; exit 1; }; "
+ "sleep 0.1; done;"
+ % (pid_var, self.DEBUG_SERVER_NAME))
def initialize(self):
"""Called after construction to generate any required config files."""
@@ -128,7 +151,7 @@ class DebuggerCrossConfig:
def _target_start_cmd(self, mode):
raise NotImplementedError
- def _target_kill_cmd(self):
+ def _target_stop_cmd(self, mode):
raise NotImplementedError
@@ -181,33 +204,48 @@ class GdbCrossConfig(DebuggerCrossConfig):
Returns something like:
"\"/bin/sh -c '/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example'\""
"""
+ port = self.port(server_mode)
if server_mode == DebuggerServerModes.ONCE:
- gdbserver_cmd_start = "%s --once :%s %s" % (
- self.debugger_cross.debug_server_path, self.debug_server_port, self.binary.binary_path)
- elif server_mode == DebuggerServerModes.ATTACH:
- pid_command = self.binary.pid_command
- if pid_command:
- gdbserver_cmd_start = "%s --attach :%s \\$(%s)" % (
- self.debugger_cross.debug_server_path,
- self.debug_server_port,
- pid_command)
- else:
- raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path)
- elif server_mode == DebuggerServerModes.MULTI:
- gdbserver_cmd_start = self._target_tcp_port_check_cmd() + " && exit 0; "
+ gdbserver_cmd_start = "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode)
+ gdbserver_cmd_start += "%s --once :%s %s & " % (
+ self.debugger_cross.debug_server_path, port, self.binary.binary_path)
+ gdbserver_cmd_start += "_gdbserver_pid=\\$!; "
+ gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode)
+ gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd(
+ "gdbserver_pid", mode=server_mode) + " "
+ gdbserver_cmd_start += "echo %s; wait \\$_gdbserver_pid" % (
+ self.get_debug_server_ready_marker(port))
+ elif server_mode in (DebuggerServerModes.ATTACH, DebuggerServerModes.MULTI):
+ # Both modes run a persistent server speaking the extended-remote
+ # protocol. They differ on the client side only: ATTACH attaches to
+ # a process that is already running on the target.
+ gdbserver_cmd_start = self._target_tcp_port_check_cmd(server_mode) + " && exit 0; "
gdbserver_cmd_start += "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode)
gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & _gdbserver_pid=\\$!; " % (
- self.debugger_cross.debug_server_path, self.debug_server_port,
+ self.debugger_cross.debug_server_path, port,
self._gdbserver_log_file(server_mode))
gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode)
- gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd("gdbserver_pid")
+ gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd(
+ "gdbserver_pid", mode=server_mode)
else:
raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode)
return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\""
- def _target_kill_cmd(self):
- """SSH command to kill gdbserver on the target device."""
- return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.debug_server_port
+ def _target_stop_cmd(self, server_mode):
+ """SSH command to stop gdbserver on the target device.
+
+ Stopping is based on the PID file written by the start command. Other
+ debug sessions run their own gdbserver on the target, so anything
+ matching by process name would hit them as well.
+ """
+ pid_file = self._gdbserver_pid_file(server_mode)
+ gdbserver_cmd_stop = "if test -f %s; then _gdbserver_pid=\\$(cat %s); " % (
+ pid_file, pid_file)
+ gdbserver_cmd_stop += "kill \\$_gdbserver_pid 2>/dev/null; "
+ gdbserver_cmd_stop += self._target_wait_for_process_exit_cmd(
+ "gdbserver_pid")
+ gdbserver_cmd_stop += " fi; rm -rf %s" % self._gdbserver_tmp_dir(server_mode)
+ return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\""
class LldbServerConfig(DebuggerCrossConfig):
@@ -244,10 +282,7 @@ class LldbServerConfig(DebuggerCrossConfig):
# lldb-server 21.x and the remote lldb client connects from the host.
# Start from /tmp because lldb-server creates temp files in its cwd and
# the SSH default cwd (/home/root) may not exist on a minimal image.
- if mode == DebuggerServerModes.ONCE:
- cmd = "cd /tmp && %s platform --one-shot --server --listen *:%s" % (
- lldb_server, self.debug_server_port)
- elif mode == DebuggerServerModes.MULTI:
+ if mode == DebuggerServerModes.MULTI:
pid_file = self._lldb_server_pid_file(mode)
tmp_dir = self._lldb_server_tmp_dir(mode)
log_file = self._lldb_server_log_file(mode)
@@ -261,11 +296,11 @@ class LldbServerConfig(DebuggerCrossConfig):
"lldb_server_pid", log_file)
else:
raise DevtoolError(
- "lldb-server does not support mode %s "
- "(ATTACH is handled client-side with 'process attach')" % mode)
+ "lldb-server only supports MULTI mode; "
+ "ATTACH is handled client-side with 'process attach': %s" % mode)
return "\"/bin/sh -c '" + cmd + "'\""
- def _target_kill_cmd(self):
+ def _target_stop_cmd(self, server_mode):
"""SSH command to stop a MULTI-mode lldb-server on the target."""
pid_file = self._lldb_server_pid_file(DebuggerServerModes.MULTI)
tmp_dir = self._lldb_server_tmp_dir(DebuggerServerModes.MULTI)
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
index d190858d44..8ccc611c98 100644
--- a/scripts/lib/devtool/ide_plugins/ide_code.py
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -32,14 +32,12 @@ class GdbCrossConfigVSCode(GdbCrossConfig):
self._target_start_cmd(mode)
]
- def target_ssh_gdbserver_kill_args(self):
- """Get the ssh command arguments to kill gdbserver on the target device
-
- returns something like:
- ['-p', '2222', 'root@target', '"kill $(pgrep -o -f \'gdbserver --attach :1234\') 2>/dev/null || true"']
- """
+ def target_ssh_gdbserver_stop_args(self, mode=None):
+ """Get the ssh command arguments to stop gdbserver on the target device"""
+ if mode is None:
+ mode = self.default_mode
return self._target_ssh_args() + [
- self._target_kill_cmd()
+ self._target_stop_cmd(mode)
]
@@ -59,10 +57,12 @@ class LldbServerConfigVSCode(LldbServerConfig):
self._target_start_cmd(mode)
]
- def target_ssh_gdbserver_kill_args(self):
+ def target_ssh_gdbserver_stop_args(self, mode=None):
"""SSH argument list to stop a running MULTI-mode lldb-server"""
+ if mode is None:
+ mode = self.default_mode
return self._target_ssh_args() + [
- self._target_kill_cmd()
+ self._target_stop_cmd(mode)
]
class IdeVSCode(IdeBase):
@@ -338,25 +338,43 @@ class IdeVSCode(IdeBase):
return self._vscode_launch_bin_dbg_lldb(cross_debug_config, server_mode)
return self._vscode_launch_bin_dbg_gdb(cross_debug_config, server_mode)
+ @staticmethod
+ def _stop_task_label(cross_debug_config, server_mode):
+ return "stop_%s_%s" % (cross_debug_config.DEBUG_SERVER_NAME,
+ cross_debug_config.id_pretty_mode(server_mode))
+
def _vscode_launch_bin_dbg_gdb(self, cross_debug_config, server_mode):
"""Generate a cppdbg (GDB) launch configuration entry for launch.json."""
modified_recipe = cross_debug_config.modified_recipe
+ is_attach = server_mode == DebuggerServerModes.ATTACH
+
launch_config = {
"name": cross_debug_config.id_pretty_mode(server_mode),
"type": "cppdbg",
- "request": "launch",
+ "request": "attach" if is_attach else "launch",
"program": cross_debug_config.binary.binary_host_path,
- "stopAtEntry": True,
- "cwd": "${workspaceFolder}",
- "environment": [],
- "externalConsole": False,
"MIMode": "gdb",
"preLaunchTask": cross_debug_config.id_pretty_mode(server_mode),
"miDebuggerPath": modified_recipe.debugger_cross.gdb,
- "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.debug_server_port)
+ "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.port(server_mode))
}
+ if is_attach:
+ # Without useExtendedRemote, cppdbg rejects attaching to a remote
+ # target. It also makes cppdbg offer a picker listing the processes
+ # running on the target, so the PID does not have to be known when
+ # this configuration is generated. Stopping the session detaches
+ # from the process instead of killing it.
+ launch_config["useExtendedRemote"] = True
+ else:
+ # cwd, environment and externalConsole configure the process the
+ # debugger starts, they are not part of the attach schema.
+ launch_config["cwd"] = "${workspaceFolder}"
+ launch_config["environment"] = []
+ launch_config["externalConsole"] = False
+ launch_config["stopAtEntry"] = True
+
# Search for header files in recipe-sysroot.
src_file_map = {
"/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include")
@@ -415,10 +433,10 @@ class IdeVSCode(IdeBase):
launch_config['sourceFileMap'] = src_file_map
launch_config['setupCommands'] = setup_commands
- # Add postDebugTask for attach mode to clean up gdbserver
- if server_mode == DebuggerServerModes.ATTACH:
- kill_task_label = "kill_gdbserver_" + cross_debug_config.id_pretty_mode(server_mode)
- launch_config["postDebugTask"] = kill_task_label
+ if is_attach:
+ # The extended-remote server outlives the debug session
+ launch_config["postDebugTask"] = self._stop_task_label(
+ cross_debug_config, server_mode)
return launch_config
@@ -574,8 +592,9 @@ class IdeVSCode(IdeBase):
if cross_debug_config.modified_recipe is not modified_recipe:
continue
for server_mode in cross_debug_config.server_modes():
- if server_mode == DebuggerServerModes.MULTI:
- # MULTI mode: the SSH command blocks until the port is ready
+ if server_mode in (DebuggerServerModes.MULTI,
+ DebuggerServerModes.ATTACH):
+ # The SSH command blocks until the port is ready
# (wait loop in _target_start_cmd), so VSCode treats this as
# a regular non-background task.
new_task = {
@@ -586,7 +605,7 @@ class IdeVSCode(IdeBase):
"problemMatcher": []
}
else:
- # ONCE / ATTACH: gdbserver runs in the foreground for the
+ # ONCE: gdbserver runs in the foreground for the
# whole session, so VSCode needs isBackground + a pattern
# matcher to avoid waiting for the task to exit.
new_task = {
@@ -608,7 +627,7 @@ class IdeVSCode(IdeBase):
"background": {
"activeOnStart": True,
"beginsPattern": ".",
- "endsPattern": ".",
+ "endsPattern": cross_debug_config.get_debug_server_ready_marker_pattern(),
}
}
]
@@ -621,28 +640,20 @@ class IdeVSCode(IdeBase):
tasks_dict['tasks'].append(new_task)
- # For attach mode, add a kill task to stop a previously running gdbserver
- # This is a known issue with gdbserver --attach that it does not terminate
- # after detaching. With this helper task, it is possible to:
- # 1. Start debugging in attach mode
- # 2. Add breakpoints, step, continue, etc.
- # 3. Press the Continue button
- # 4. Press the Stop button which detaches gdbserver from the debugged process
- # 5. Start debugging again in attach mode
- # Without this kill task, step 5 would fail because gdbserver is still running
+ # The extended-remote server used by attach mode keeps running
+ # after the debug session, launch.json refers to this task as
+ # postDebugTask.
if server_mode == DebuggerServerModes.ATTACH:
- new_task_kill_label = "kill_gdbserver_"+ cross_debug_config.id_pretty_mode(server_mode)
- new_task_kill = {
- "label": new_task_kill_label,
+ tasks_dict['tasks'].append({
+ "label": self._stop_task_label(cross_debug_config, server_mode),
"type": "shell",
"command": cross_debug_config.debugger_cross.target_device.ssh_sshexec,
- "args": cross_debug_config.target_ssh_gdbserver_kill_args(),
+ "args": cross_debug_config.target_ssh_gdbserver_stop_args(server_mode),
"presentation": {
"close": True
},
"problemMatcher": []
- }
- tasks_dict['tasks'].append(new_task_kill)
+ })
tasks_file = 'tasks.json'
IdeBase.update_json_file(
@@ -804,8 +815,9 @@ class IdeVSCode(IdeBase):
if cross_debug_config.modified_recipe is not modified_recipe:
continue
for server_mode in cross_debug_config.server_modes():
- if server_mode == DebuggerServerModes.MULTI:
- # MULTI mode: SSH command blocks until port is ready, treat as
+ if server_mode in (DebuggerServerModes.MULTI,
+ DebuggerServerModes.ATTACH):
+ # SSH command blocks until port is ready, treat as
# a regular non-background task (same as vscode_tasks_cpp).
new_task = {
"label": cross_debug_config.id_pretty_mode(server_mode),
@@ -815,7 +827,7 @@ class IdeVSCode(IdeBase):
"problemMatcher": []
}
else:
- # ONCE / ATTACH: server runs for the whole session, needs
+ # ONCE: server runs for the whole session, needs
# isBackground so VSCode does not wait for the task to exit.
new_task = {
"label": cross_debug_config.id_pretty_mode(server_mode),
diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py
index a8ddc3f39f..959140cedb 100644
--- a/scripts/lib/devtool/ide_plugins/ide_none.py
+++ b/scripts/lib/devtool/ide_plugins/ide_none.py
@@ -21,16 +21,19 @@ class GdbCrossConfigNone(GdbCrossConfig):
default_mode)
def _target_gdbserver_stop_cmd(self, server_mode):
- """Kill a gdbserver process"""
- # This is the usual behavior: gdbserver is stopped on demand
- if server_mode == DebuggerServerModes.MULTI:
- gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % (
- self._gdbserver_pid_file(server_mode), self._gdbserver_pid_file(server_mode))
- gdbserver_cmd_stop += " rm -rf %s" % self._gdbserver_tmp_dir(server_mode)
- # This is unexpected since gdbserver should terminate after each debug session
- # Just kill all gdbserver instances to keep it simple
- else:
- gdbserver_cmd_stop = "killall gdbserver"
+ """Kill a gdbserver process
+
+ Stopping is based on the PID file written by the start command. Other
+ debug sessions run their own gdbserver on the target, so anything
+ matching by process name would hit them as well.
+ """
+ pid_file = self._gdbserver_pid_file(server_mode)
+ gdbserver_cmd_stop = "if test -f %s; then _gdbserver_pid=\\$(cat %s); " % (
+ pid_file, pid_file)
+ gdbserver_cmd_stop += "kill \\$_gdbserver_pid 2>/dev/null; "
+ gdbserver_cmd_stop += self._target_wait_for_process_exit_cmd(
+ "gdbserver_pid")
+ gdbserver_cmd_stop += " fi; rm -rf %s" % self._gdbserver_tmp_dir(server_mode)
return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\""
def _gen_gdbserver_start_script(self, server_mode=None):
@@ -165,14 +168,19 @@ class LldbServerConfigNone(LldbServerConfig):
return os.path.join(self.script_dir, 'lldb_' + self.id_pretty)
def _target_lldb_server_stop_cmd(self, server_mode):
- """SSH command to stop lldb-server on the target."""
- if server_mode == DebuggerServerModes.MULTI:
- pid_file = self._lldb_server_pid_file(server_mode)
- tmp_dir = self._lldb_server_tmp_dir(server_mode)
- cmd = ("test -f %(pf)s && kill \\$(cat %(pf)s) 2>/dev/null; rm -rf %(td)s"
- % {'pf': pid_file, 'td': tmp_dir})
- else:
- cmd = "killall lldb-server 2>/dev/null || true"
+ """SSH command to stop lldb-server on the target.
+
+ Stopping is based on the PID file written by the start command. Other
+ debug sessions run their own lldb-server on the target, so anything
+ matching by process name would hit them as well.
+ """
+ pid_file = self._lldb_server_pid_file(server_mode)
+ tmp_dir = self._lldb_server_tmp_dir(server_mode)
+ cmd = "if test -f %s; then _lldb_server_pid=\\$(cat %s); " % (
+ pid_file, pid_file)
+ cmd += "kill \\$_lldb_server_pid 2>/dev/null; "
+ cmd += self._target_wait_for_process_exit_cmd("lldb_server_pid")
+ cmd += " fi; rm -rf %s" % tmp_dir
return "\"/bin/sh -c '" + cmd + "'\""
def _gen_lldb_server_start_script(self, server_mode=None):
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 11/14] oe-selftest: devtool ide-sdk: wait for gdbserver readiness
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (9 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 10/14] devtool: ide-sdk: attach via extended-remote AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 12/14] oe-selftest: devtool ide-sdk: verify debugger shutdown AdrianF
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Replace the fixed delay and process-list probe for GDB background tasks
with a wait for the readiness marker generated in tasks.json. This
verifies the same background-task contract VS Code uses before starting
the debugger session.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 36 ++++++++++++++++++-------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 35abc6a48f..461ebf5900 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2811,6 +2811,22 @@ class RunCmdBackground:
def __enter__(self):
self.cmd.run()
+ return self
+
+ def output(self):
+ return b"".join(self.cmd._output_chunks).decode(
+ "utf-8", errors="replace")
+
+ def wait_for_output(self, pattern, timeout):
+ pattern = re.compile(pattern, re.MULTILINE)
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if pattern.search(self.output()):
+ return True
+ if self.cmd.process.poll() is not None:
+ break
+ time.sleep(0.1)
+ return False
def __exit__(self, exc_type, exc_val, exc_tb):
self.cmd.stop()
@@ -3595,16 +3611,16 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
if len(ssh_gdbserver_cmd) > 0 and ssh_gdbserver_cmd[-1].startswith('"') and ssh_gdbserver_cmd[-1].endswith('"'):
ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1].replace('\\$', '$') # Remove surrounding quotes
self.logger.debug(f"Starting gdbserver with command: {' '.join(ssh_gdbserver_cmd)}")
- with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger):
- # Give gdbserver a moment to start
- time.sleep(1)
-
- # Verify gdbserver is running on target and listening on expected port
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "Failed to check processes on target")
- self.assertIn("gdbserver", result.output, "gdbserver should be running on target")
- _, server_port = server_addr.split(':')
- self.assertIn(server_port, result.output, f"gdbserver should be listening on port {server_port}")
+ _, server_port = server_addr.split(':')
+ with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger) as gdbserver:
+ ready_pattern = prelaunch_task["problemMatcher"][0]["background"]["endsPattern"]
+ # Must exceed the target side budget (TARGET_START_RETRIES * 0.1s),
+ # otherwise this gives up while the target is still waiting and its
+ # diagnostics never make it into the failure.
+ self.assertTrue(
+ gdbserver.wait_for_output(ready_pattern, timeout=60),
+ "gdbserver did not report readiness on port %s:\n%s" %
+ (server_port, gdbserver.output()))
if debug_func and debug_check_func:
# Do a gdb remote session using the once configuration
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 12/14] oe-selftest: devtool ide-sdk: verify debugger shutdown
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (10 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 11/14] oe-selftest: devtool ide-sdk: wait for gdbserver readiness AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 13/14] oe-selftest: devtool ide-sdk: use qemu.run for target checks AdrianF
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Use the QEMU target command interface to inspect the generated ide=none
server PID files and process command lines. Verify that the stop helpers
remove both the tracked process and its PID file, without relying on
broad ps output.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 61 +++++++++++--------------
1 file changed, 27 insertions(+), 34 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 461ebf5900..3a99a493b5 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3278,7 +3278,6 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
numbers after recompiling, to prove the breakpoints resolve via the
freshly rebuilt debug info.
"""
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
gdbserver_script = os.path.join(self._workspace_scripts_dir(
recipe_name), 'gdbserver_1234_usr-bin-' + example_exe + '_multi')
gdb_script = os.path.join(self._workspace_scripts_dir(
@@ -3288,19 +3287,18 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
r = runCmd(gdbserver_script, output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Check there is a gdbserver running
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn("gdbserver ", r.output)
+ pid_file = '/tmp/gdbserver_1234_usr-bin-%s_multi/gdbserver.pid' % example_exe
+ status, output = qemu.run('cat %s' % pid_file)
+ self.assertEqual(status, 0)
+ gdbserver_pid = output.strip()
+ self.assertRegex(gdbserver_pid, r'^\d+$')
- # Check the pid file is correct
- test_cmd = "'cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \
- example_exe + "_multi/gdbserver.pid)/cmdline'"
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn("gdbserver", r.output)
- self.assertIn("--multi", r.output)
- self.assertIn("1234", r.output)
+ # Check the pid file identifies the expected gdbserver process
+ status, output = qemu.run('cat /proc/%s/cmdline' % gdbserver_pid)
+ self.assertEqual(status, 0)
+ self.assertIn("gdbserver", output)
+ self.assertIn("--multi", output)
+ self.assertIn("1234", output)
# Test remote debugging works
gdb_batch_cmd = " --batch " + self._gdb_debug_cpp_example(
@@ -3317,10 +3315,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
r = runCmd(gdbserver_script + ' stop', output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Check there is no gdbserver running
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertNotIn("gdbserver ", r.output)
+ # The stop script waits for its recorded PID before it succeeds.
+ status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % (
+ gdbserver_pid, pid_file))
+ self.assertEqual(status, 0)
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_qemu(self):
@@ -4489,7 +4487,6 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests):
lldbinit source map / debug-file-search-paths setup for the library's
own debug info specifically.
"""
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
scripts_dir = self._workspace_scripts_dir(recipe_name)
binary_pretty = 'usr-bin-' + example_exe
lldb_server_script = os.path.join(
@@ -4503,19 +4500,16 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests):
r = runCmd(lldb_server_script, output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Verify lldb-server is running on the target
- r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn('lldb-server', r.output)
+ pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty
+ status, output = qemu.run('cat %s' % pid_file)
+ self.assertEqual(status, 0)
+ lldb_server_pid = output.strip()
+ self.assertRegex(lldb_server_pid, r'^\d+$')
# Verify the pid file points at the running lldb-server process
- pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty
- test_cmd = "'cat /proc/$(cat %s)/cmdline'" % pid_file
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn('lldb-server', r.output)
+ status, output = qemu.run('cat /proc/%s/cmdline' % lldb_server_pid)
+ self.assertEqual(status, 0)
+ self.assertIn('lldb-server', output)
# Run an lldb batch session covering the executable, library and
# header breakpoints, then continue to completion
@@ -4530,11 +4524,10 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests):
r = runCmd(lldb_server_script + ' stop', output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Verify lldb-server is no longer running
- r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertNotIn('lldb-server', r.output)
+ # The stop script waits for its recorded PID before it succeeds.
+ status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % (
+ lldb_server_pid, pid_file))
+ self.assertEqual(status, 0)
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_cmake(self):
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 13/14] oe-selftest: devtool ide-sdk: use qemu.run for target checks
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (11 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 12/14] oe-selftest: devtool ide-sdk: verify debugger shutdown AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 6:30 ` [PATCH v2 14/14] oe-selftest: devtool ide-sdk: verify attach debug workflow AdrianF
2026-08-18 13:36 ` [OE-core] [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup Mathieu Dubois-Briand
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Use QEMU target commands instead of spawning a separate ssh process for
simple target-side executable checks in the ide=code selftest helper.
This keeps the test consistent with the rest of the qemu-based cleanup
and avoids duplicating SSH option handling in the test itself.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 3a99a493b5..ab846b8dc3 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3593,13 +3593,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
self.assertEqual(task_command, "ssh", f"Task '{prelaunch_task_name}' should use ssh command")
self.assertTrue(len(task_args) >= 2, f"Task '{prelaunch_task_name}' should have at least 2 args (ssh options and remote command)")
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x /usr/bin/gdbserver'),
- output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "gdbserver should be installed on target")
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x ' + os.path.join('/usr/bin', example_exe)),
- output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "Example binary should be installed on target")
+ status, _ = qemu.run('test -x /usr/bin/gdbserver')
+ self.assertEqual(status, 0, "gdbserver should be installed on target")
+ status, _ = qemu.run('test -x ' + os.path.join('/usr/bin', example_exe))
+ self.assertEqual(status, 0, "Example binary should be installed on target")
# Start gdbserver on target using the task command (keep the ssh connection open while debugging)
ssh_gdbserver_cmd = [task_command] + task_args
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v2 14/14] oe-selftest: devtool ide-sdk: verify attach debug workflow
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (12 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 13/14] oe-selftest: devtool ide-sdk: use qemu.run for target checks AdrianF
@ 2026-08-18 6:30 ` AdrianF
2026-08-18 13:36 ` [OE-core] [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup Mathieu Dubois-Briand
14 siblings, 0 replies; 16+ messages in thread
From: AdrianF @ 2026-08-18 6:30 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Add attach-mode debug verification in ide=code selftests and document
the intended execution flow:
- Verify attach postDebugTask labels against tasks.json.
- Run attach checks before once checks to avoid Build-ID mismatch from
once/deploy side effects on the still-running service process.
- In attach mode, set a temporary breakpoint on sleep(), continue once
to hit it, then detach immediately.
This verifies debugger control flow against the already-running loop
without fragile inferior expression evaluation.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 152 +++++++++++++++++++-----
1 file changed, 119 insertions(+), 33 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index ab846b8dc3..ef9ebf0f30 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3399,10 +3399,19 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
def _verify_launch_json(self, tempdir):
"""Verify the launch.json file created is valid and contains proper debug configurations"""
launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json')
+ tasks_json_path = os.path.join(tempdir, '.vscode', 'tasks.json')
self.assertTrue(os.path.exists(launch_json_path), "launch.json file should exist")
+ self.assertTrue(os.path.exists(tasks_json_path), "tasks.json file should exist")
with open(launch_json_path) as launch_j:
launch_d = json.load(launch_j)
+ with open(tasks_json_path) as tasks_j:
+ tasks_d = json.load(tasks_j)
+ task_labels = {
+ task.get("label")
+ for task in tasks_d.get("tasks", [])
+ if task.get("label")
+ }
self.assertIn("configurations", launch_d)
configurations = launch_d["configurations"]
@@ -3480,6 +3489,8 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
self.assertIn("_attach", task, f"attach configuration '{config_name}' should have attach preLaunchTask")
# Verify postDebugTask exists for attach configurations (kill gdbserver)
self.assertIn("postDebugTask", config, f"attach configuration '{config_name}' should have postDebugTask")
+ self.assertIn(config["postDebugTask"], task_labels,
+ f"attach configuration '{config_name}' postDebugTask should exist in tasks.json")
# Categorize configurations
config_name = config["name"]
@@ -3495,34 +3506,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
"Each debug configuration should use a distinct server address")
def _verify_launch_json_debugging(self, tempdir, qemu, example_exe):
- """Verify remote debugging and deployment works using launch.json configurations
+ """Verify launch.json debugging configs for one target executable.
- This method tests the VSCode debug configurations by:
- 1. Starting gdbserver on target using tasks.json commands
- 2. Running gdb debugging session with batch commands
- $BUILDDIR/tmp/work/x86_64-linux/gdb-cross-x86_64/16.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-gdb --batch \
- -ex 'set sysroot $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image' \
- -ex 'set substitute-path /usr/include $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/recipe-sysroot/usr/include' \
- -ex 'set substitute-path /usr/src/debug/cmake-example/1.0 $BUILDDIR/workspace/sources/cmake-example' \
- -ex 'set substitute-path /usr/src/debug $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/src/debug' \
- -ex 'set solib-search-path $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib/.debug:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/.debug:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/debug:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/lib:\
- $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/usr/lib' \
- -ex 'file $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image/usr/bin/cmake-example' \
- -ex 'target remote 192.168.7.2:1234' \
- -ex 'break main' \
- -ex 'continue' \
- -ex 'break CppExample::print_json()' \
- -ex 'continue' \
- -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789")' \
- -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789aaa")' \
- -ex 'list cpp-example-lib.hpp:15,15' \
- -ex 'continue'
- 3. Verifying debug output and stopping gdbserver
+ Runs attach-mode first against the already-running service process,
+ then validates the once configuration. This ordering avoids Build-ID
+ mismatch from once/deploy side effects before attach is tested.
"""
with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_j:
launch_d = json.load(launch_j)
@@ -3538,18 +3526,116 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests):
# as the recipe name (e.g. meson-example installs a binary named
# "mesonex").
once_config_count = 0
+ attach_config_count = 0
+
+ # Run attach first. The attach test targets the already-running
+ # service process in the image; running a once configuration first can
+ # deploy/update binaries and trigger Build-ID mismatch warnings against
+ # that still-running process.
+ for config in configurations:
+ if f"usr-bin-{example_exe}_attach" in config["name"]:
+ attach_config_count += 1
+ self._verify_launch_config_attach(tempdir, config, qemu, example_exe)
+
for config in configurations:
if f"usr-bin-{example_exe}_once" in config["name"]:
once_config_count += 1
self._verify_launch_config(tempdir, config, tasks, qemu, example_exe,
self._gdb_debug_cpp_example, self._gdb_debug_cpp_example_check)
- # It works but is not 100% reliable in VSCode
- # This one: https://github.com/microsoft/vscode-cpptools/issues/4243 ?
- # elif f"usr-bin-{example_exe}_attach" in config["name"]
- # self._verify_launch_config(tempdir, config, tasks, qemu, example_exe)
- else:
- continue
self.assertEqual(once_config_count, 1, f"Should have one once configuration, found: {once_config_count}")
+ self.assertEqual(attach_config_count, 1, f"Should have one attach configuration, found: {attach_config_count}")
+
+ def _verify_launch_config_attach(self, tempdir, launch_config, qemu, example_exe):
+ """Verify attach-mode debugging by exercising debugger control flow.
+
+ The attached service runs an endless loop, and each loop iteration
+ executes sleep(). We break on sleep() to prove that attach-mode gdb is
+ controlling the already-running process at a point that is expected to
+ be reached repeatedly during normal service execution, then detach.
+ """
+ self.assertEqual(launch_config.get("request"), "attach")
+
+ tasks_path = os.path.join(tempdir, '.vscode', 'tasks.json')
+ self.assertExists(tasks_path, 'tasks.json not found at %s' % tasks_path)
+ with open(tasks_path) as tasks_j:
+ tasks_d = json.load(tasks_j)
+ task_by_label = {
+ task.get('label'): task
+ for task in tasks_d.get('tasks', [])
+ if task.get('label')
+ }
+
+ debugger_path = launch_config["miDebuggerPath"]
+ server_addr = launch_config["miDebuggerServerAddress"]
+ prelaunch_task_name = launch_config["preLaunchTask"]
+ post_debug_task_name = launch_config.get("postDebugTask")
+ program = launch_config["program"]
+ additional_so_lib_search_path = launch_config["additionalSOLibSearchPath"]
+ source_file_map = launch_config["sourceFileMap"]
+ setup_commands = launch_config["setupCommands"]
+
+ self.assertTrue(post_debug_task_name,
+ "attach configuration should define postDebugTask")
+ self.assertIn(prelaunch_task_name, task_by_label)
+ self.assertIn(post_debug_task_name, task_by_label)
+
+ status, _ = qemu.run('test -x /usr/bin/gdbserver')
+ self.assertEqual(status, 0, "gdbserver should be installed on target")
+ status, _ = qemu.run('test -x ' + os.path.join('/usr/bin', example_exe))
+ self.assertEqual(status, 0, "Example binary should be installed on target")
+
+ # The attach preLaunchTask has no dependsOn (it attaches to an already
+ # running service instead of building/deploying), so it can be run
+ # directly without a dependency chain runner.
+ prelaunch_task = task_by_label[prelaunch_task_name]
+ prelaunch_cmd = [prelaunch_task["command"]] + [str(arg)
+ for arg in prelaunch_task.get("args", [])]
+ # The tasks.json command is shell-quoted and escapes '$' for an
+ # intermediate shell, so undo that before passing argv to subprocess.
+ if prelaunch_cmd[-1].startswith('"') and prelaunch_cmd[-1].endswith('"'):
+ prelaunch_cmd[-1] = prelaunch_cmd[-1][1:-1].replace('\\$', '$')
+ runCmd(prelaunch_cmd, output_log=self._cmd_logger)
+
+ # Attach to the service process currently running on the target.
+ status, output = qemu.run("pgrep '^%s$'" % example_exe)
+ self.assertEqual(status, 0, msg="%s service not running: %s" %
+ (example_exe, output))
+ service_pid = output.strip()
+ self.assertRegex(service_pid, r'^\d+$')
+
+ gdb_batch_cmd = debugger_path + " --batch"
+ for setup_command in setup_commands:
+ setup_cmd = setup_command["text"].strip()
+ if setup_cmd.startswith("-"):
+ continue
+ gdb_batch_cmd += ' -ex ' + shlex.quote(setup_cmd)
+ for k, v in source_file_map.items():
+ gdb_batch_cmd += " -ex 'set substitute-path %s %s'" % (k, v.replace("${workspaceFolder}", tempdir))
+ gdb_batch_cmd += " -ex 'set solib-search-path %s'" % additional_so_lib_search_path
+ gdb_batch_cmd += " -ex 'file %s'" % program
+ gdb_batch_cmd += " -ex 'target extended-remote %s'" % server_addr
+ gdb_batch_cmd += " -ex 'attach %s'" % service_pid
+ gdb_batch_cmd += " -ex 'set breakpoint pending on'"
+ gdb_batch_cmd += " -ex 'tbreak sleep'"
+ gdb_batch_cmd += " -ex 'continue'"
+ gdb_batch_cmd += " -ex 'detach'"
+ self.logger.debug(f"Starting attach gdb session with command: {gdb_batch_cmd}")
+
+ r = runCmd(gdb_batch_cmd, output_log=self._cmd_logger)
+ self.assertEqual(r.status, 0)
+ self.assertIn("sleep", r.output)
+
+ # Stop the persistent attach server and verify the service survives.
+ post_debug_task = task_by_label[post_debug_task_name]
+ post_debug_cmd = [post_debug_task["command"]] + [str(arg)
+ for arg in post_debug_task.get("args", [])]
+ if post_debug_cmd[-1].startswith('"') and post_debug_cmd[-1].endswith('"'):
+ post_debug_cmd[-1] = post_debug_cmd[-1][1:-1].replace('\\$', '$')
+ runCmd(post_debug_cmd, output_log=self._cmd_logger)
+
+ status, output = qemu.run("pgrep '^%s$'" % example_exe)
+ self.assertEqual(status, 0, msg="%s service should still be running after attach stop: %s" %
+ (example_exe, output))
def _verify_launch_config(self, tempdir, launch_config, tasks, qemu, example_exe, debug_func=None, debug_check_func=None):
self.assertIsNotNone(launch_config, "Should have at least one launch debug configuration")
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [OE-core] [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup
2026-08-18 6:30 [PATCH v2 00/14] devtool: ide-sdk: robust attach-mode debugging and test cleanup AdrianF
` (13 preceding siblings ...)
2026-08-18 6:30 ` [PATCH v2 14/14] oe-selftest: devtool ide-sdk: verify attach debug workflow AdrianF
@ 2026-08-18 13:36 ` Mathieu Dubois-Briand
14 siblings, 0 replies; 16+ messages in thread
From: Mathieu Dubois-Briand @ 2026-08-18 13:36 UTC (permalink / raw)
To: adrian.freihofer, openembedded-core
On Tue Aug 18, 2026 at 8:30 AM CEST, Adrian Freihofer via lists.openembedded.org wrote:
> From: Adrian Freihofer <adrian.freihofer@siemens.com>
>
> Changes from v1 to v2:
>
> On autobuilder the the test_devtool_ide_sdk_code_cmake and
> test_devtool_ide_sdk_code_meson tests failed with:
>
> File "/srv/pokybuild/yocto-worker/oe-selftest-armhost/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3695, in _verify_launch_config
> self.assertTrue(
> File "/usr/lib/python3.12/unittest/case.py", line 727, in assertTrue
> raise self.failureException(msg)
> AssertionError: False is not true : gdbserver did not report readiness on port 1234
>
> Probably this could happen when the attach tests start a gdbserver and then
> the once mode tests reuses the same port for gdbserver --once before the first
> gdbserver --multi released the port and terminated. This is fixed by using
> different ports for different debugging modes which can theoretically also be
> used concurently.
>
Hi Adrian,
I'm sorry, but we still have failures on the same tests :(
2026-08-18 11:15:37,934 - oe-selftest - INFO - devtool.DevtoolIdeSdkGccTests.test_devtool_ide_sdk_code_cmake (subunit.RemotedTestCase)
2026-08-18 11:15:37,938 - oe-selftest - INFO - ... FAIL
...
2026-08-18 11:15:37,938 - oe-selftest - INFO - 12: 2/73 134/766 (398.86s) (0 failed) (devtool.DevtoolIdeSdkGccTests.test_devtool_ide_sdk_code_cmake)
2026-08-18 11:15:37,938 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3759, in test_devtool_ide_sdk_code_cmake
self._verify_launch_json_debugging(tempdir, qemu, example_exe)
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3543, in _verify_launch_json_debugging
self._verify_launch_config(tempdir, config, tasks, qemu, example_exe,
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3701, in _verify_launch_config
self.assertTrue(
File "/usr/lib64/python3.12/unittest/case.py", line 727, in assertTrue
raise self.failureException(msg)
AssertionError: False is not true : gdbserver did not report readiness on port 1234:
...
2026-08-18 11:21:24,242 - oe-selftest - INFO - devtool.DevtoolIdeSdkGccTests.test_devtool_ide_sdk_code_meson (subunit.RemotedTestCase)
2026-08-18 11:21:24,242 - oe-selftest - INFO - ... FAIL
...
2026-08-18 11:21:24,242 - oe-selftest - INFO - 12: 3/73 280/766 (346.31s) (2 failed) (devtool.DevtoolIdeSdkGccTests.test_devtool_ide_sdk_code_meson)
2026-08-18 11:21:24,242 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3816, in test_devtool_ide_sdk_code_meson
self._verify_launch_json_debugging(tempdir, qemu, example_exe)
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3543, in _verify_launch_json_debugging
self._verify_launch_config(tempdir, config, tasks, qemu, example_exe,
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3701, in _verify_launch_config
self.assertTrue(
File "/usr/lib64/python3.12/unittest/case.py", line 727, in assertTrue
raise self.failureException(msg)
AssertionError: False is not true : gdbserver did not report readiness on port 1234:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/4410
https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/4590
https://autobuilder.yoctoproject.org/valkyrie/#/builders/23/builds/4640
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread