Changes in 5.15.75
Revert "fs: check FMODE_LSEEK to control internal pipe splicing"
ALSA: oss: Fix potential deadlock at unregistration
ALSA: rawmidi: Drop register_mutex in snd_rawmidi_free()
ALSA: usb-audio: Fix potential memory leaks
ALSA: usb-audio: Fix NULL dererence at error path
ALSA: hda/realtek: remove ALC289_FIXUP_DUAL_SPK for Dell 5530
ALSA: hda/realtek: Correct pin configs for ASUS G533Z
ALSA: hda/realtek: Add quirk for ASUS GV601R laptop
ALSA: hda/realtek: Add Intel Reference SSID to support headset keys
mtd: rawnand: atmel: Unmap streaming DMA mappings
io_uring/net: don't update msg_name if not provided
hv_netvsc: Fix race between VF offering and VF association message from host
cifs: destage dirty pages before re-reading them for cache=none
cifs: Fix the error length of VALIDATE_NEGOTIATE_INFO message
iio: dac: ad5593r: Fix i2c read protocol requirements
iio: ltc2497: Fix reading conversion results
iio: adc: ad7923: fix channel readings for some variants
iio: pressure: dps310: Refactor startup procedure
iio: pressure: dps310: Reset chip after timeout
xhci: dbc: Fix memory leak in xhci_alloc_dbc()
usb: add quirks for Lenovo OneLink+ Dock
can: kvaser_usb: Fix use of uninitialized completion
can: kvaser_usb_leaf: Fix overread with an invalid command
can: kvaser_usb_leaf: Fix TX queue out of sync after restart
can: kvaser_usb_leaf: Fix CAN state after restart
mmc: sdhci-sprd: Fix minimum clock limit
i2c: designware: Fix handling of real but unexpected device interrupts
fs: dlm: fix race between test_bit() and queue_work()
fs: dlm: handle -EBUSY first in lock arg validation
HID: multitouch: Add memory barriers
quota: Check next/prev free block number after reading from quota file
platform/chrome: cros_ec_proto: Update version on GET_NEXT_EVENT failure
ASoC: wcd9335: fix order of Slimbus unprepare/disable
ASoC: wcd934x: fix order of Slimbus unprepare/disable
hwmon: (gsc-hwmon) Call of_node_get() before of_find_xxx API
net: thunderbolt: Enable DMA paths only after rings are enabled
regulator: qcom_rpm: Fix circular deferral regression
arm64: topology: move store_cpu_topology() to shared code
riscv: topology: fix default topology reporting
RISC-V: Make port I/O string accessors actually work
parisc: fbdev/stifb: Align graphics memory size to 4MB
riscv: Allow PROT_WRITE-only mmap()
riscv: Make VM_WRITE imply VM_READ
riscv: always honor the CONFIG_CMDLINE_FORCE when parsing dtb
riscv: Pass -mno-relax only on lld < 15.0.0
UM: cpuinfo: Fix a warning for CONFIG_CPUMASK_OFFSTACK
nvmem: core: Fix memleak in nvmem_register()
nvme-multipath: fix possible hang in live ns resize with ANA access
nvme-pci: set min_align_mask before calculating max_hw_sectors
Revert "drm/amdgpu: use dirty framebuffer helper"
dmaengine: mxs: use platform_driver_register
drm/virtio: Check whether transferred 2D BO is shmem
drm/virtio: Unlock reservations on virtio_gpu_object_shmem_init() error
drm/virtio: Use appropriate atomic state in virtio_gpu_plane_cleanup_fb()
drm/udl: Restore display mode on resume
arm64: errata: Add Cortex-A55 to the repeat tlbi list
mm/damon: validate if the pmd entry is present before accessing
mm/mmap: undo ->mmap() when arch_validate_flags() fails
xen/gntdev: Prevent leaking grants
xen/gntdev: Accommodate VMA splitting
PCI: Sanitise firmware BAR assignments behind a PCI-PCI bridge
serial: 8250: Let drivers request full 16550A feature probing
serial: 8250: Request full 16550A feature probing for OxSemi PCIe devices
NFSD: Protect against send buffer overflow in NFSv3 READDIR
NFSD: Protect against send buffer overflow in NFSv2 READ
NFSD: Protect against send buffer overflow in NFSv3 READ
powercap: intel_rapl: Use standard Energy Unit for SPR Dram RAPL domain
powerpc/boot: Explicitly disable usage of SPE instructions
slimbus: qcom-ngd: use correct error in message of pdr_add_lookup() failure
slimbus: qcom-ngd: cleanup in probe error path
scsi: qedf: Populate sysfs attributes for vport
gpio: rockchip: request GPIO mux to pinctrl when setting direction
pinctrl: rockchip: add pinmux_ops.gpio_set_direction callback
fbdev: smscufx: Fix use-after-free in ufx_ops_open()
ksmbd: fix endless loop when encryption for response fails
ksmbd: Fix wrong return value and message length check in smb2_ioctl()
ksmbd: Fix user namespace mapping
fs: record I_DIRTY_TIME even if inode already has I_DIRTY_INODE
btrfs: fix race between quota enable and quota rescan ioctl
btrfs: set generation before calling btrfs_clean_tree_block in btrfs_init_new_buffer
f2fs: complete checkpoints during remount
f2fs: flush pending checkpoints when freezing super
f2fs: increase the limit for reserve_root
f2fs: fix to do sanity check on destination blkaddr during recovery
f2fs: fix to do sanity check on summary info
hardening: Avoid harmless Clang option under CONFIG_INIT_STACK_ALL_ZERO
hardening: Remove Clang's enable flag for -ftrivial-auto-var-init=zero
jbd2: wake up journal waiters in FIFO order, not LIFO
jbd2: fix potential buffer head reference count leak
jbd2: fix potential use-after-free in jbd2_fc_wait_bufs
jbd2: add miss release buffer head in fc_do_one_pass()
ext4: avoid crash when inline data creation follows DIO write
ext4: fix null-ptr-deref in ext4_write_info
ext4: make ext4_lazyinit_thread freezable
ext4: fix check for block being out of directory size
ext4: don't increase iversion counter for ea_inodes
ext4: ext4_read_bh_lock() should submit IO if the buffer isn't uptodate
ext4: place buffer head allocation before handle start
ext4: fix dir corruption when ext4_dx_add_entry() fails
ext4: fix miss release buffer head in ext4_fc_write_inode
ext4: fix potential memory leak in ext4_fc_record_modified_inode()
ext4: fix potential memory leak in ext4_fc_record_regions()
ext4: update 'state->fc_regions_size' after successful memory allocation
livepatch: fix race between fork and KLP transition
ftrace: Properly unset FTRACE_HASH_FL_MOD
ring-buffer: Allow splice to read previous partially read pages
ring-buffer: Have the shortest_full queue be the shortest not longest
ring-buffer: Check pending waiters when doing wake ups as well
ring-buffer: Add ring_buffer_wake_waiters()
ring-buffer: Fix race between reset page and reading page
tracing: Disable interrupt or preemption before acquiring arch_spinlock_t
tracing: Wake up ring buffer waiters on closing of the file
tracing: Wake up waiters when tracing is disabled
tracing: Add ioctl() to force ring buffer waiters to wake up
tracing: Move duplicate code of trace_kprobe/eprobe.c into header
tracing: Add "(fault)" name injection to kernel probes
tracing: Fix reading strings from synthetic events
thunderbolt: Explicitly enable lane adapter hotplug events at startup
efi: libstub: drop pointless get_memory_map() call
media: cedrus: Set the platform driver data earlier
media: cedrus: Fix endless loop in cedrus_h265_skip_bits()
blk-wbt: call rq_qos_add() after wb_normal is initialized
KVM: x86/emulator: Fix handing of POP SS to correctly set interruptibility
KVM: nVMX: Unconditionally purge queued/injected events on nested "exit"
KVM: nVMX: Don't propagate vmcs12's PERF_GLOBAL_CTRL settings to vmcs02
KVM: VMX: Drop bits 31:16 when shoving exception error code into VMCS
staging: greybus: audio_helper: remove unused and wrong debugfs usage
drm/nouveau/kms/nv140-: Disable interlacing
drm/nouveau: fix a use-after-free in nouveau_gem_prime_import_sg_table()
drm/i915: Fix watermark calculations for gen12+ RC CCS modifier
drm/i915: Fix watermark calculations for gen12+ MC CCS modifier
drm/i915: Fix watermark calculations for gen12+ CCS+CC modifier
drm/amd/display: Fix vblank refcount in vrr transition
smb3: must initialize two ACL struct fields to zero
selinux: use "grep -E" instead of "egrep"
ima: fix blocking of security.ima xattrs of unsupported algorithms
userfaultfd: open userfaultfds with O_RDONLY
ntfs3: rework xattr handlers and switch to POSIX ACL VFS helpers
thermal: cpufreq_cooling: Check the policy first in cpufreq_cooling_register()
sh: machvec: Use char[] for section boundaries
MIPS: SGI-IP27: Free some unused memory
MIPS: SGI-IP27: Fix platform-device leak in bridge_platform_create()
ARM: 9244/1: dump: Fix wrong pg_level in walk_pmd()
ARM: 9247/1: mm: set readonly for MT_MEMORY_RO with ARM_LPAE
objtool: Preserve special st_shndx indexes in elf_update_symbol
nfsd: Fix a memory leak in an error handling path
SUNRPC: Fix svcxdr_init_decode's end-of-buffer calculation
SUNRPC: Fix svcxdr_init_encode's buflen calculation
NFSD: Protect against send buffer overflow in NFSv2 READDIR
NFSD: Fix handling of oversized NFSv4 COMPOUND requests
wifi: rtlwifi: 8192de: correct checking of IQK reload
wifi: ath10k: add peer map clean up for peer delete in ath10k_sta_state()
leds: lm3601x: Don't use mutex after it was destroyed
bpf: Fix reference state management for synchronous callbacks
wifi: mac80211: allow bw change during channel switch in mesh
bpftool: Fix a wrong type cast in btf_dumper_int
spi: mt7621: Fix an error message in mt7621_spi_probe()
x86/resctrl: Fix to restore to original value when re-enabling hardware prefetch register
xsk: Fix backpressure mechanism on Tx
bpf: Disable preemption when increasing per-cpu map_locked
bpf: Propagate error from htab_lock_bucket() to userspace
bpf: Use this_cpu_{inc|dec|inc_return} for bpf_task_storage_busy
Bluetooth: btusb: mediatek: fix WMT failure during runtime suspend
wifi: rtl8xxxu: tighten bounds checking in rtl8xxxu_read_efuse()
wifi: rtw88: add missing destroy_workqueue() on error path in rtw_core_init()
selftests/xsk: Avoid use-after-free on ctx
spi: qup: add missing clk_disable_unprepare on error in spi_qup_resume()
spi: qup: add missing clk_disable_unprepare on error in spi_qup_pm_resume_runtime()
wifi: rtl8xxxu: Fix skb misuse in TX queue selection
spi: meson-spicc: do not rely on busy flag in pow2 clk ops
bpf: btf: fix truncated last_member_type_id in btf_struct_resolve
wifi: rtl8xxxu: gen2: Fix mistake in path B IQ calibration
wifi: rtl8xxxu: Remove copy-paste leftover in gen2_update_rate_mask
wifi: mt76: sdio: fix transmitting packet hangs
wifi: mt76: mt7615: add mt7615_mutex_acquire/release in mt7615_sta_set_decap_offload
wifi: mt76: mt7915: do not check state before configuring implicit beamform
Bluetooth: RFCOMM: Fix possible deadlock on socket shutdown/release
net: fs_enet: Fix wrong check in do_pd_setup
bpf: Ensure correct locking around vulnerable function find_vpid()
Bluetooth: hci_{ldisc,serdev}: check percpu_init_rwsem() failure
netfilter: conntrack: fix the gc rescheduling delay
netfilter: conntrack: revisit the gc initial rescheduling bias
wifi: ath11k: fix number of VHT beamformee spatial streams
x86/microcode/AMD: Track patch allocation size explicitly
x86/cpu: Include the header of init_ia32_feat_ctl()'s prototype
spi: dw: Fix PM disable depth imbalance in dw_spi_bt1_probe
spi/omap100k:Fix PM disable depth imbalance in omap1_spi100k_probe
skmsg: Schedule psock work if the cached skb exists on the psock
i2c: mlxbf: support lock mechanism
Bluetooth: hci_core: Fix not handling link timeouts propertly
xfrm: Reinject transport-mode packets through workqueue
netfilter: nft_fib: Fix for rpath check with VRF devices
spi: s3c64xx: Fix large transfers with DMA
wifi: rtl8xxxu: Fix AIFS written to REG_EDCA_*_PARAM
vhost/vsock: Use kvmalloc/kvfree for larger packets.
eth: alx: take rtnl_lock on resume
mISDN: fix use-after-free bugs in l1oip timer handlers
sctp: handle the error returned from sctp_auth_asoc_init_active_key
tcp: fix tcp_cwnd_validate() to not forget is_cwnd_limited
spi: Ensure that sg_table won't be used after being freed
hwmon: (pmbus/mp2888) Fix sensors readouts for MPS Multi-phase mp2888 controller
net: rds: don't hold sock lock when cancelling work from rds_tcp_reset_callbacks()
bnx2x: fix potential memory leak in bnx2x_tpa_stop()
net: wwan: iosm: Call mutex_init before locking it
net/ieee802154: reject zero-sized raw_sendmsg()
once: add DO_ONCE_SLOW() for sleepable contexts
net: mvpp2: fix mvpp2 debugfs leak
drm: bridge: adv7511: fix CEC power down control register offset
drm: bridge: adv7511: unregister cec i2c device after cec adapter
drm/bridge: Avoid uninitialized variable warning
drm/mipi-dsi: Detach devices when removing the host
drm/virtio: Correct drm_gem_shmem_get_sg_table() error handling
drm/bridge: parade-ps8640: Fix regulator supply order
drm/dp_mst: fix drm_dp_dpcd_read return value checks
drm:pl111: Add of_node_put() when breaking out of for_each_available_child_of_node()
ASoC: mt6359: fix tests for platform_get_irq() failure
platform/chrome: fix double-free in chromeos_laptop_prepare()
platform/chrome: fix memory corruption in ioctl
ASoC: tas2764: Allow mono streams
ASoC: tas2764: Drop conflicting set_bias_level power setting
ASoC: tas2764: Fix mute/unmute
platform/x86: msi-laptop: Fix old-ec check for backlight registering
platform/x86: msi-laptop: Fix resource cleanup
platform/chrome: cros_ec_typec: Correct alt mode index
drm/amdgpu: add missing pci_disable_device() in amdgpu_pmops_runtime_resume()
drm/bridge: megachips: Fix a null pointer dereference bug
ASoC: rsnd: Add check for rsnd_mod_power_on
ALSA: hda: beep: Simplify keep-power-at-enable behavior
drm/bochs: fix blanking
drm/omap: dss: Fix refcount leak bugs
drm/amdgpu: Fix memory leak in hpd_rx_irq_create_workqueue()
mmc: au1xmmc: Fix an error handling path in au1xmmc_probe()
ASoC: eureka-tlv320: Hold reference returned from of_find_xxx API
drm/msm/dpu: index dpu_kms->hw_vbif using vbif_idx
drm/msm/dp: correct 1.62G link rate at dp_catalog_ctrl_config_msa()
drm/vmwgfx: Fix memory leak in vmw_mksstat_add_ioctl()
ASoC: codecs: tx-macro: fix kcontrol put
ASoC: da7219: Fix an error handling path in da7219_register_dai_clks()
ALSA: dmaengine: increment buffer pointer atomically
mmc: wmt-sdmmc: Fix an error handling path in wmt_mci_probe()
ASoC: wm8997: Fix PM disable depth imbalance in wm8997_probe
ASoC: wm5110: Fix PM disable depth imbalance in wm5110_probe
ASoC: wm5102: Fix PM disable depth imbalance in wm5102_probe
ASoC: mt6660: Fix PM disable depth imbalance in mt6660_i2c_probe
ALSA: hda/hdmi: Don't skip notification handling during PM operation
memory: pl353-smc: Fix refcount leak bug in pl353_smc_probe()
memory: of: Fix refcount leak bug in of_get_ddr_timings()
memory: of: Fix refcount leak bug in of_lpddr3_get_ddr_timings()
locks: fix TOCTOU race when granting write lease
soc: qcom: smsm: Fix refcount leak bugs in qcom_smsm_probe()
soc: qcom: smem_state: Add refcounting for the 'state->of_node'
ARM: dts: imx6qdl-kontron-samx6i: hook up DDC i2c bus
ARM: dts: turris-omnia: Fix mpp26 pin name and comment
ARM: dts: kirkwood: lsxl: fix serial line
ARM: dts: kirkwood: lsxl: remove first ethernet port
ia64: export memory_add_physaddr_to_nid to fix cxl build error
soc/tegra: fuse: Drop Kconfig dependency on TEGRA20_APB_DMA
arm64: dts: ti: k3-j7200: fix main pinmux range
ARM: dts: exynos: correct s5k6a3 reset polarity on Midas family
ARM: Drop CMDLINE_* dependency on ATAGS
ext4: don't run ext4lazyinit for read-only filesystems
arm64: ftrace: fix module PLTs with mcount
ARM: dts: exynos: fix polarity of VBUS GPIO of Origen
iio: adc: at91-sama5d2_adc: fix AT91_SAMA5D2_MR_TRACKTIM_MAX
iio: adc: at91-sama5d2_adc: check return status for pressure and touch
iio: adc: at91-sama5d2_adc: lock around oversampling and sample freq
iio: adc: at91-sama5d2_adc: disable/prepare buffer on suspend/resume
iio: inkern: only release the device node when done with it
iio: inkern: fix return value in devm_of_iio_channel_get_by_name()
iio: ABI: Fix wrong format of differential capacitance channel ABI.
iio: magnetometer: yas530: Change data type of hard_offsets to signed
RDMA/mlx5: Don't compare mkey tags in DEVX indirect mkey
usb: common: debug: Check non-standard control requests
clk: meson: Hold reference returned by of_get_parent()
clk: oxnas: Hold reference returned by of_get_parent()
clk: qoriq: Hold reference returned by of_get_parent()
clk: berlin: Add of_node_put() for of_get_parent()
clk: sprd: Hold reference returned by of_get_parent()
clk: tegra: Fix refcount leak in tegra210_clock_init
clk: tegra: Fix refcount leak in tegra114_clock_init
clk: tegra20: Fix refcount leak in tegra20_clock_init
HSI: omap_ssi: Fix refcount leak in ssi_probe
HSI: omap_ssi_port: Fix dma_map_sg error check
media: exynos4-is: fimc-is: Add of_node_put() when breaking out of loop
tty: xilinx_uartps: Fix the ignore_status
media: meson: vdec: add missing clk_disable_unprepare on error in vdec_hevc_start()
media: uvcvideo: Fix memory leak in uvc_gpio_parse
media: uvcvideo: Use entity get_cur in uvc_ctrl_set
media: xilinx: vipp: Fix refcount leak in xvip_graph_dma_init
RDMA/rxe: Fix "kernel NULL pointer dereference" error
RDMA/rxe: Fix the error caused by qp->sk
misc: ocxl: fix possible refcount leak in afu_ioctl()
fpga: prevent integer overflow in dfl_feature_ioctl_set_irq()
dmaengine: hisilicon: Disable channels when unregister hisi_dma
dmaengine: hisilicon: Fix CQ head update
dmaengine: hisilicon: Add multi-thread support for a DMA channel
dyndbg: fix static_branch manipulation
dyndbg: fix module.dyndbg handling
dyndbg: let query-modname override actual module name
dyndbg: drop EXPORTed dynamic_debug_exec_queries
clk: qcom: sm6115: Select QCOM_GDSC
mtd: devices: docg3: check the return value of devm_ioremap() in the probe
phy: amlogic: phy-meson-axg-mipi-pcie-analog: Hold reference returned by of_get_parent()
phy: phy-mtk-tphy: fix the phy type setting issue
mtd: rawnand: intel: Read the chip-select line from the correct OF node
mtd: rawnand: intel: Remove undocumented compatible string
mtd: rawnand: fsl_elbc: Fix none ECC mode
RDMA/irdma: Align AE id codes to correct flush code and event
RDMA/srp: Fix srp_abort()
RDMA/siw: Always consume all skbuf data in sk_data_ready() upcall.
RDMA/siw: Fix QP destroy to wait for all references dropped.
ata: fix ata_id_sense_reporting_enabled() and ata_id_has_sense_reporting()
ata: fix ata_id_has_devslp()
ata: fix ata_id_has_ncq_autosense()
ata: fix ata_id_has_dipm()
mtd: rawnand: meson: fix bit map use in meson_nfc_ecc_correct()
md: Replace snprintf with scnprintf
md/raid5: Ensure stripe_fill happens on non-read IO with journal
md/raid5: Remove unnecessary bio_put() in raid5_read_one_chunk()
RDMA/cm: Use SLID in the work completion as the DLID in responder side
IB: Set IOVA/LENGTH on IB_MR in core/uverbs layers
xhci: Don't show warning for reinit on known broken suspend
usb: gadget: function: fix dangling pnp_string in f_printer.c
drivers: serial: jsm: fix some leaks in probe
serial: 8250: Toggle IER bits on only after irq has been set up
tty: serial: fsl_lpuart: disable dma rx/tx use flags in lpuart_dma_shutdown
phy: qualcomm: call clk_disable_unprepare in the error handling
staging: vt6655: fix some erroneous memory clean-up loops
slimbus: qcom-ngd-ctrl: allow compile testing without QCOM_RPROC_COMMON
firmware: google: Test spinlock on panic path to avoid lockups
serial: 8250: Fix restoring termios speed after suspend
scsi: libsas: Fix use-after-free bug in smp_execute_task_sg()
scsi: iscsi: Rename iscsi_conn_queue_work()
scsi: iscsi: Add recv workqueue helpers
scsi: iscsi: Run recv path from workqueue
scsi: iscsi: iscsi_tcp: Fix null-ptr-deref while calling getpeername()
clk: qcom: apss-ipq6018: mark apcs_alias0_core_clk as critical
clk: qcom: gcc-sm6115: Override default Alpha PLL regs
RDMA/rxe: Fix resize_finish() in rxe_queue.c
fsi: core: Check error number after calling ida_simple_get
mfd: intel_soc_pmic: Fix an error handling path in intel_soc_pmic_i2c_probe()
mfd: fsl-imx25: Fix an error handling path in mx25_tsadc_setup_irq()
mfd: lp8788: Fix an error handling path in lp8788_probe()
mfd: lp8788: Fix an error handling path in lp8788_irq_init() and lp8788_irq_init()
mfd: fsl-imx25: Fix check for platform_get_irq() errors
mfd: sm501: Add check for platform_driver_register()
clk: mediatek: mt8183: mfgcfg: Propagate rate changes to parent
dmaengine: ioat: stop mod_timer from resurrecting deleted timer in __cleanup()
usb: mtu3: fix failed runtime suspend in host only mode
spmi: pmic-arb: correct duplicate APID to PPID mapping logic
clk: vc5: Fix 5P49V6901 outputs disabling when enabling FOD
clk: baikal-t1: Fix invalid xGMAC PTP clock divider
clk: baikal-t1: Add shared xGMAC ref/ptp clocks internal parent
clk: baikal-t1: Add SATA internal ref clock buffer
clk: bcm2835: fix bcm2835_clock_rate_from_divisor declaration
clk: imx: scu: fix memleak on platform_device_add() fails
clk: ti: dra7-atl: Fix reference leak in of_dra7_atl_clk_probe
clk: ast2600: BCLK comes from EPLL
mailbox: mpfs: fix handling of the reg property
mailbox: mpfs: account for mbox offsets while sending
mailbox: bcm-ferxrm-mailbox: Fix error check for dma_map_sg
powerpc/configs: Properly enable PAPR_SCM in pseries_defconfig
powerpc/math_emu/efp: Include module.h
powerpc/sysdev/fsl_msi: Add missing of_node_put()
powerpc/pci_dn: Add missing of_node_put()
powerpc/powernv: add missing of_node_put() in opal_export_attrs()
powerpc: Fix fallocate and fadvise64_64 compat parameter combination
x86/hyperv: Fix 'struct hv_enlightened_vmcs' definition
powerpc/64s: Fix GENERIC_CPU build flags for PPC970 / G5
powerpc: Fix SPE Power ISA properties for e500v1 platforms
powerpc/kprobes: Fix null pointer reference in arch_prepare_kprobe()
powerpc/pseries/vas: Pass hw_cpu_id to node associativity HCALL
crypto: sahara - don't sleep when in softirq
crypto: hisilicon/zip - fix mismatch in get/set sgl_sge_nr
hwrng: arm-smccc-trng - fix NO_ENTROPY handling
cgroup: Honor caller's cgroup NS when resolving path
hwrng: imx-rngc - Moving IRQ handler registering after imx_rngc_irq_mask_clear()
crypto: qat - fix default value of WDT timer
crypto: hisilicon/qm - fix missing put dfx access
cgroup/cpuset: Enable update_tasks_cpumask() on top_cpuset
iommu/omap: Fix buffer overflow in debugfs
crypto: akcipher - default implementation for setting a private key
crypto: ccp - Release dma channels before dmaengine unrgister
crypto: inside-secure - Change swab to swab32
crypto: qat - fix DMA transfer direction
cifs: return correct error in ->calc_signature()
iommu/iova: Fix module config properly
tracing: kprobe: Fix kprobe event gen test module on exit
tracing: kprobe: Make gen test module work in arm and riscv
tracing/osnoise: Fix possible recursive locking in stop_per_cpu_kthreads
kbuild: remove the target in signal traps when interrupted
kbuild: rpm-pkg: fix breakage when V=1 is used
crypto: marvell/octeontx - prevent integer overflows
crypto: cavium - prevent integer overflow loading firmware
thermal/drivers/qcom/tsens-v0_1: Fix MSM8939 fourth sensor hw_id
ACPI: APEI: do not add task_work to kernel thread to avoid memory leak
f2fs: fix race condition on setting FI_NO_EXTENT flag
f2fs: fix to account FS_CP_DATA_IO correctly
selftest: tpm2: Add Client.__del__() to close /dev/tpm* handle
fs: dlm: fix race in lowcomms
rcu: Avoid triggering strict-GP irq-work when RCU is idle
rcu: Back off upon fill_page_cache_func() allocation failure
rcu-tasks: Convert RCU_LOCKDEP_WARN() to WARN_ONCE()
ACPI: video: Add Toshiba Satellite/Portege Z830 quirk
ACPI: tables: FPDT: Don't call acpi_os_map_memory() on invalid phys address
cpufreq: intel_pstate: Add Tigerlake support in no-HWP mode
MIPS: BCM47XX: Cast memcmp() of function to (void *)
powercap: intel_rapl: fix UBSAN shift-out-of-bounds issue
thermal: intel_powerclamp: Use get_cpu() instead of smp_processor_id() to avoid crash
ARM: decompressor: Include .data.rel.ro.local
ACPI: x86: Add a quirk for Dell Inspiron 14 2-in-1 for StorageD3Enable
x86/entry: Work around Clang __bdos() bug
NFSD: Return nfserr_serverfault if splice_ok but buf->pages have data
NFSD: fix use-after-free on source server when doing inter-server copy
wifi: brcmfmac: fix invalid address access when enabling SCAN log level
bpftool: Clear errno after libcap's checks
ice: set tx_tstamps when creating new Tx rings via ethtool
net: ethernet: ti: davinci_mdio: Add workaround for errata i2329
openvswitch: Fix double reporting of drops in dropwatch
openvswitch: Fix overreporting of drops in dropwatch
tcp: annotate data-race around tcp_md5sig_pool_populated
x86/mce: Retrieve poison range from hardware
wifi: ath9k: avoid uninit memory read in ath9k_htc_rx_msg()
thunderbolt: Add back Intel Falcon Ridge end-to-end flow control workaround
xfrm: Update ipcomp_scratches with NULL when freed
iavf: Fix race between iavf_close and iavf_reset_task
wifi: brcmfmac: fix use-after-free bug in brcmf_netdev_start_xmit()
Bluetooth: btintel: Mark Intel controller to support LE_STATES quirk
regulator: core: Prevent integer underflow
wifi: mt76: mt7921: reset msta->airtime_ac while clearing up hw value
Bluetooth: L2CAP: initialize delayed works at l2cap_chan_create()
Bluetooth: hci_sysfs: Fix attempting to call device_add multiple times
can: bcm: check the result of can_send() in bcm_can_tx()
wifi: rt2x00: don't run Rt5592 IQ calibration on MT7620
wifi: rt2x00: set correct TX_SW_CFG1 MAC register for MT7620
wifi: rt2x00: set VGC gain for both chains of MT7620
wifi: rt2x00: set SoC wmac clock register
wifi: rt2x00: correctly set BBP register 86 for MT7620
hwmon: (sht4x) do not overflow clamping operation on 32-bit platforms
net: If sock is dead don't access sock's sk_wq in sk_stream_wait_memory
Bluetooth: L2CAP: Fix user-after-free
r8152: Rate limit overflow messages
drm/nouveau/nouveau_bo: fix potential memory leak in nouveau_bo_alloc()
drm: Use size_t type for len variable in drm_copy_field()
drm: Prevent drm_copy_field() to attempt copying a NULL pointer
drm/komeda: Fix handling of atomic commits in the atomic_commit_tail hook
gpu: lontium-lt9611: Fix NULL pointer dereference in lt9611_connector_init()
drm/amd/display: fix overflow on MIN_I64 definition
udmabuf: Set ubuf->sg = NULL if the creation of sg table fails
drm: bridge: dw_hdmi: only trigger hotplug event on link change
ALSA: usb-audio: Register card at the last interface
drm/vc4: vec: Fix timings for VEC modes
drm: panel-orientation-quirks: Add quirk for Anbernic Win600
platform/chrome: cros_ec: Notify the PM of wake events during resume
platform/x86: msi-laptop: Change DMI match / alias strings to fix module autoloading
ASoC: SOF: pci: Change DMI match info to support all Chrome platforms
drm/amdgpu: fix initial connector audio value
drm/meson: reorder driver deinit sequence to fix use-after-free bug
drm/meson: explicitly remove aggregate driver at module unload time
mmc: sdhci-msm: add compatible string check for sdm670
drm/dp: Don't rewrite link config when setting phy test pattern
drm/amd/display: Remove interface for periodic interrupt 1
ARM: dts: imx7d-sdb: config the max pressure for tsc2046
ARM: dts: imx6q: add missing properties for sram
ARM: dts: imx6dl: add missing properties for sram
ARM: dts: imx6qp: add missing properties for sram
ARM: dts: imx6sl: add missing properties for sram
ARM: dts: imx6sll: add missing properties for sram
ARM: dts: imx6sx: add missing properties for sram
kselftest/arm64: Fix validatation termination record after EXTRA_CONTEXT
arm64: dts: imx8mq-librem5: Add bq25895 as max17055's power supply
btrfs: dump extra info if one free space cache has more bitmaps than it should
btrfs: scrub: try to fix super block errors
btrfs: don't print information about space cache or tree every remount
ARM: 9242/1: kasan: Only map modules if CONFIG_KASAN_VMALLOC=n
clk: zynqmp: Fix stack-out-of-bounds in strncpy`
media: cx88: Fix a null-ptr-deref bug in buffer_prepare()
media: platform: fix some double free in meson-ge2d and mtk-jpeg and s5p-mfc
clk: zynqmp: pll: rectify rate rounding in zynqmp_pll_round_rate
usb: host: xhci-plat: suspend and resume clocks
usb: host: xhci-plat: suspend/resume clks for brcm
dmaengine: ti: k3-udma: Reset UDMA_CHAN_RT byte counters to prevent overflow
scsi: 3w-9xxx: Avoid disabling device if failing to enable it
nbd: Fix hung when signal interrupts nbd_start_device_ioctl()
iommu/arm-smmu-v3: Make default domain type of HiSilicon PTT device to identity
power: supply: adp5061: fix out-of-bounds read in adp5061_get_chg_type()
staging: vt6655: fix potential memory leak
blk-throttle: prevent overflow while calculating wait time
ata: libahci_platform: Sanity check the DT child nodes number
bcache: fix set_at_max_writeback_rate() for multiple attached devices
soundwire: cadence: Don't overwrite msg->buf during write commands
soundwire: intel: fix error handling on dai registration issues
HID: roccat: Fix use-after-free in roccat_read()
eventfd: guard wake_up in eventfd fs calls as well
md/raid5: Wait for MD_SB_CHANGE_PENDING in raid5d
usb: host: xhci: Fix potential memory leak in xhci_alloc_stream_info()
usb: musb: Fix musb_gadget.c rxstate overflow bug
arm64: dts: imx8mp: Add snps,gfladj-refclk-lpm-sel quirk to USB nodes
usb: dwc3: core: Enable GUCTL1 bit 10 for fixing termination error after resume bug
Revert "usb: storage: Add quirk for Samsung Fit flash"
staging: rtl8723bs: fix potential memory leak in rtw_init_drv_sw()
staging: rtl8723bs: fix a potential memory leak in rtw_init_cmd_priv()
scsi: tracing: Fix compile error in trace_array calls when TRACING is disabled
ext2: Use kvmalloc() for group descriptor array
nvme: copy firmware_rev on each init
nvmet-tcp: add bounds check on Transfer Tag
usb: idmouse: fix an uninit-value in idmouse_open
clk: bcm2835: Make peripheral PLLC critical
clk: bcm2835: Round UART input clock up
perf intel-pt: Fix segfault in intel_pt_print_info() with uClibc
io_uring/af_unix: defer registered files gc to io_uring release
io_uring: correct pinned_vm accounting
io_uring/rw: fix short rw error handling
io_uring/rw: fix error'ed retry return values
io_uring/rw: fix unexpected link breakage
mm: hugetlb: fix UAF in hugetlb_handle_userfault
net: ieee802154: return -EINVAL for unknown addr type
ALSA: usb-audio: Fix last interface check for registration
blk-wbt: fix that 'rwb->wc' is always set to 1 in wbt_init()
net: ethernet: ti: davinci_mdio: fix build for mdio bitbang uses
Revert "net/ieee802154: reject zero-sized raw_sendmsg()"
net/ieee802154: don't warn zero-sized raw_sendmsg()
drm/amd/display: Fix build breakage with CONFIG_DEBUG_FS=n
Kconfig.debug: simplify the dependency of DEBUG_INFO_DWARF4/5
Kconfig.debug: add toolchain checks for DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT
lib/Kconfig.debug: Add check for non-constant .{s,u}leb128 support to DWARF5
ext4: continue to expand file system when the target size doesn't reach
thermal: intel_powerclamp: Use first online CPU as control_cpu
gcov: support GCC 12.1 and newer compilers
io-wq: Fix memory leak in worker creation
Linux 5.15.75
Change-Id: I5a3ef9688fb31003940d7e1828f863b9d50f1da9
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2072 lines
50 KiB
C
2072 lines
50 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Kprobes-based tracing events
|
|
*
|
|
* Created by Masami Hiramatsu <mhiramat@redhat.com>
|
|
*
|
|
*/
|
|
#define pr_fmt(fmt) "trace_kprobe: " fmt
|
|
|
|
#include <linux/security.h>
|
|
#include <linux/module.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/error-injection.h>
|
|
|
|
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
|
|
|
|
#include "trace_dynevent.h"
|
|
#include "trace_kprobe_selftest.h"
|
|
#include "trace_probe.h"
|
|
#include "trace_probe_tmpl.h"
|
|
#include "trace_probe_kernel.h"
|
|
|
|
#define KPROBE_EVENT_SYSTEM "kprobes"
|
|
#define KRETPROBE_MAXACTIVE_MAX 4096
|
|
|
|
/* Kprobe early definition from command line */
|
|
static char kprobe_boot_events_buf[COMMAND_LINE_SIZE] __initdata;
|
|
|
|
static int __init set_kprobe_boot_events(char *str)
|
|
{
|
|
strlcpy(kprobe_boot_events_buf, str, COMMAND_LINE_SIZE);
|
|
disable_tracing_selftest("running kprobe events");
|
|
|
|
return 1;
|
|
}
|
|
__setup("kprobe_event=", set_kprobe_boot_events);
|
|
|
|
static int trace_kprobe_create(const char *raw_command);
|
|
static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev);
|
|
static int trace_kprobe_release(struct dyn_event *ev);
|
|
static bool trace_kprobe_is_busy(struct dyn_event *ev);
|
|
static bool trace_kprobe_match(const char *system, const char *event,
|
|
int argc, const char **argv, struct dyn_event *ev);
|
|
|
|
static struct dyn_event_operations trace_kprobe_ops = {
|
|
.create = trace_kprobe_create,
|
|
.show = trace_kprobe_show,
|
|
.is_busy = trace_kprobe_is_busy,
|
|
.free = trace_kprobe_release,
|
|
.match = trace_kprobe_match,
|
|
};
|
|
|
|
/*
|
|
* Kprobe event core functions
|
|
*/
|
|
struct trace_kprobe {
|
|
struct dyn_event devent;
|
|
struct kretprobe rp; /* Use rp.kp for kprobe use */
|
|
unsigned long __percpu *nhit;
|
|
const char *symbol; /* symbol name */
|
|
struct trace_probe tp;
|
|
};
|
|
|
|
static bool is_trace_kprobe(struct dyn_event *ev)
|
|
{
|
|
return ev->ops == &trace_kprobe_ops;
|
|
}
|
|
|
|
static struct trace_kprobe *to_trace_kprobe(struct dyn_event *ev)
|
|
{
|
|
return container_of(ev, struct trace_kprobe, devent);
|
|
}
|
|
|
|
/**
|
|
* for_each_trace_kprobe - iterate over the trace_kprobe list
|
|
* @pos: the struct trace_kprobe * for each entry
|
|
* @dpos: the struct dyn_event * to use as a loop cursor
|
|
*/
|
|
#define for_each_trace_kprobe(pos, dpos) \
|
|
for_each_dyn_event(dpos) \
|
|
if (is_trace_kprobe(dpos) && (pos = to_trace_kprobe(dpos)))
|
|
|
|
static nokprobe_inline bool trace_kprobe_is_return(struct trace_kprobe *tk)
|
|
{
|
|
return tk->rp.handler != NULL;
|
|
}
|
|
|
|
static nokprobe_inline const char *trace_kprobe_symbol(struct trace_kprobe *tk)
|
|
{
|
|
return tk->symbol ? tk->symbol : "unknown";
|
|
}
|
|
|
|
static nokprobe_inline unsigned long trace_kprobe_offset(struct trace_kprobe *tk)
|
|
{
|
|
return tk->rp.kp.offset;
|
|
}
|
|
|
|
static nokprobe_inline bool trace_kprobe_has_gone(struct trace_kprobe *tk)
|
|
{
|
|
return !!(kprobe_gone(&tk->rp.kp));
|
|
}
|
|
|
|
static nokprobe_inline bool trace_kprobe_within_module(struct trace_kprobe *tk,
|
|
struct module *mod)
|
|
{
|
|
int len = strlen(module_name(mod));
|
|
const char *name = trace_kprobe_symbol(tk);
|
|
|
|
return strncmp(module_name(mod), name, len) == 0 && name[len] == ':';
|
|
}
|
|
|
|
static nokprobe_inline bool trace_kprobe_module_exist(struct trace_kprobe *tk)
|
|
{
|
|
char *p;
|
|
bool ret;
|
|
|
|
if (!tk->symbol)
|
|
return false;
|
|
p = strchr(tk->symbol, ':');
|
|
if (!p)
|
|
return true;
|
|
*p = '\0';
|
|
rcu_read_lock_sched();
|
|
ret = !!find_module(tk->symbol);
|
|
rcu_read_unlock_sched();
|
|
*p = ':';
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool trace_kprobe_is_busy(struct dyn_event *ev)
|
|
{
|
|
struct trace_kprobe *tk = to_trace_kprobe(ev);
|
|
|
|
return trace_probe_is_enabled(&tk->tp);
|
|
}
|
|
|
|
static bool trace_kprobe_match_command_head(struct trace_kprobe *tk,
|
|
int argc, const char **argv)
|
|
{
|
|
char buf[MAX_ARGSTR_LEN + 1];
|
|
|
|
if (!argc)
|
|
return true;
|
|
|
|
if (!tk->symbol)
|
|
snprintf(buf, sizeof(buf), "0x%p", tk->rp.kp.addr);
|
|
else if (tk->rp.kp.offset)
|
|
snprintf(buf, sizeof(buf), "%s+%u",
|
|
trace_kprobe_symbol(tk), tk->rp.kp.offset);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%s", trace_kprobe_symbol(tk));
|
|
if (strcmp(buf, argv[0]))
|
|
return false;
|
|
argc--; argv++;
|
|
|
|
return trace_probe_match_command_args(&tk->tp, argc, argv);
|
|
}
|
|
|
|
static bool trace_kprobe_match(const char *system, const char *event,
|
|
int argc, const char **argv, struct dyn_event *ev)
|
|
{
|
|
struct trace_kprobe *tk = to_trace_kprobe(ev);
|
|
|
|
return strcmp(trace_probe_name(&tk->tp), event) == 0 &&
|
|
(!system || strcmp(trace_probe_group_name(&tk->tp), system) == 0) &&
|
|
trace_kprobe_match_command_head(tk, argc, argv);
|
|
}
|
|
|
|
static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk)
|
|
{
|
|
unsigned long nhit = 0;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu)
|
|
nhit += *per_cpu_ptr(tk->nhit, cpu);
|
|
|
|
return nhit;
|
|
}
|
|
|
|
static nokprobe_inline bool trace_kprobe_is_registered(struct trace_kprobe *tk)
|
|
{
|
|
return !(list_empty(&tk->rp.kp.list) &&
|
|
hlist_unhashed(&tk->rp.kp.hlist));
|
|
}
|
|
|
|
/* Return 0 if it fails to find the symbol address */
|
|
static nokprobe_inline
|
|
unsigned long trace_kprobe_address(struct trace_kprobe *tk)
|
|
{
|
|
unsigned long addr;
|
|
|
|
if (tk->symbol) {
|
|
addr = (unsigned long)
|
|
kallsyms_lookup_name(trace_kprobe_symbol(tk));
|
|
if (addr)
|
|
addr += tk->rp.kp.offset;
|
|
} else {
|
|
addr = (unsigned long)tk->rp.kp.addr;
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
static nokprobe_inline struct trace_kprobe *
|
|
trace_kprobe_primary_from_call(struct trace_event_call *call)
|
|
{
|
|
struct trace_probe *tp;
|
|
|
|
tp = trace_probe_primary_from_call(call);
|
|
if (WARN_ON_ONCE(!tp))
|
|
return NULL;
|
|
|
|
return container_of(tp, struct trace_kprobe, tp);
|
|
}
|
|
|
|
bool trace_kprobe_on_func_entry(struct trace_event_call *call)
|
|
{
|
|
struct trace_kprobe *tk = trace_kprobe_primary_from_call(call);
|
|
|
|
return tk ? (kprobe_on_func_entry(tk->rp.kp.addr,
|
|
tk->rp.kp.addr ? NULL : tk->rp.kp.symbol_name,
|
|
tk->rp.kp.addr ? 0 : tk->rp.kp.offset) == 0) : false;
|
|
}
|
|
|
|
bool trace_kprobe_error_injectable(struct trace_event_call *call)
|
|
{
|
|
struct trace_kprobe *tk = trace_kprobe_primary_from_call(call);
|
|
|
|
return tk ? within_error_injection_list(trace_kprobe_address(tk)) :
|
|
false;
|
|
}
|
|
|
|
static int register_kprobe_event(struct trace_kprobe *tk);
|
|
static int unregister_kprobe_event(struct trace_kprobe *tk);
|
|
|
|
static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs);
|
|
static int kretprobe_dispatcher(struct kretprobe_instance *ri,
|
|
struct pt_regs *regs);
|
|
|
|
static void free_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
if (tk) {
|
|
trace_probe_cleanup(&tk->tp);
|
|
kfree(tk->symbol);
|
|
free_percpu(tk->nhit);
|
|
kfree(tk);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate new trace_probe and initialize it (including kprobes).
|
|
*/
|
|
static struct trace_kprobe *alloc_trace_kprobe(const char *group,
|
|
const char *event,
|
|
void *addr,
|
|
const char *symbol,
|
|
unsigned long offs,
|
|
int maxactive,
|
|
int nargs, bool is_return)
|
|
{
|
|
struct trace_kprobe *tk;
|
|
int ret = -ENOMEM;
|
|
|
|
tk = kzalloc(struct_size(tk, tp.args, nargs), GFP_KERNEL);
|
|
if (!tk)
|
|
return ERR_PTR(ret);
|
|
|
|
tk->nhit = alloc_percpu(unsigned long);
|
|
if (!tk->nhit)
|
|
goto error;
|
|
|
|
if (symbol) {
|
|
tk->symbol = kstrdup(symbol, GFP_KERNEL);
|
|
if (!tk->symbol)
|
|
goto error;
|
|
tk->rp.kp.symbol_name = tk->symbol;
|
|
tk->rp.kp.offset = offs;
|
|
} else
|
|
tk->rp.kp.addr = addr;
|
|
|
|
if (is_return)
|
|
tk->rp.handler = kretprobe_dispatcher;
|
|
else
|
|
tk->rp.kp.pre_handler = kprobe_dispatcher;
|
|
|
|
tk->rp.maxactive = maxactive;
|
|
INIT_HLIST_NODE(&tk->rp.kp.hlist);
|
|
INIT_LIST_HEAD(&tk->rp.kp.list);
|
|
|
|
ret = trace_probe_init(&tk->tp, event, group, false);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
dyn_event_init(&tk->devent, &trace_kprobe_ops);
|
|
return tk;
|
|
error:
|
|
free_trace_kprobe(tk);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static struct trace_kprobe *find_trace_kprobe(const char *event,
|
|
const char *group)
|
|
{
|
|
struct dyn_event *pos;
|
|
struct trace_kprobe *tk;
|
|
|
|
for_each_trace_kprobe(tk, pos)
|
|
if (strcmp(trace_probe_name(&tk->tp), event) == 0 &&
|
|
strcmp(trace_probe_group_name(&tk->tp), group) == 0)
|
|
return tk;
|
|
return NULL;
|
|
}
|
|
|
|
static inline int __enable_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (trace_kprobe_is_registered(tk) && !trace_kprobe_has_gone(tk)) {
|
|
if (trace_kprobe_is_return(tk))
|
|
ret = enable_kretprobe(&tk->rp);
|
|
else
|
|
ret = enable_kprobe(&tk->rp.kp);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __disable_trace_kprobe(struct trace_probe *tp)
|
|
{
|
|
struct trace_probe *pos;
|
|
struct trace_kprobe *tk;
|
|
|
|
list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
|
|
tk = container_of(pos, struct trace_kprobe, tp);
|
|
if (!trace_kprobe_is_registered(tk))
|
|
continue;
|
|
if (trace_kprobe_is_return(tk))
|
|
disable_kretprobe(&tk->rp);
|
|
else
|
|
disable_kprobe(&tk->rp.kp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enable trace_probe
|
|
* if the file is NULL, enable "perf" handler, or enable "trace" handler.
|
|
*/
|
|
static int enable_trace_kprobe(struct trace_event_call *call,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct trace_probe *pos, *tp;
|
|
struct trace_kprobe *tk;
|
|
bool enabled;
|
|
int ret = 0;
|
|
|
|
tp = trace_probe_primary_from_call(call);
|
|
if (WARN_ON_ONCE(!tp))
|
|
return -ENODEV;
|
|
enabled = trace_probe_is_enabled(tp);
|
|
|
|
/* This also changes "enabled" state */
|
|
if (file) {
|
|
ret = trace_probe_add_file(tp, file);
|
|
if (ret)
|
|
return ret;
|
|
} else
|
|
trace_probe_set_flag(tp, TP_FLAG_PROFILE);
|
|
|
|
if (enabled)
|
|
return 0;
|
|
|
|
list_for_each_entry(pos, trace_probe_probe_list(tp), list) {
|
|
tk = container_of(pos, struct trace_kprobe, tp);
|
|
if (trace_kprobe_has_gone(tk))
|
|
continue;
|
|
ret = __enable_trace_kprobe(tk);
|
|
if (ret)
|
|
break;
|
|
enabled = true;
|
|
}
|
|
|
|
if (ret) {
|
|
/* Failed to enable one of them. Roll back all */
|
|
if (enabled)
|
|
__disable_trace_kprobe(tp);
|
|
if (file)
|
|
trace_probe_remove_file(tp, file);
|
|
else
|
|
trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Disable trace_probe
|
|
* if the file is NULL, disable "perf" handler, or disable "trace" handler.
|
|
*/
|
|
static int disable_trace_kprobe(struct trace_event_call *call,
|
|
struct trace_event_file *file)
|
|
{
|
|
struct trace_probe *tp;
|
|
|
|
tp = trace_probe_primary_from_call(call);
|
|
if (WARN_ON_ONCE(!tp))
|
|
return -ENODEV;
|
|
|
|
if (file) {
|
|
if (!trace_probe_get_file_link(tp, file))
|
|
return -ENOENT;
|
|
if (!trace_probe_has_single_file(tp))
|
|
goto out;
|
|
trace_probe_clear_flag(tp, TP_FLAG_TRACE);
|
|
} else
|
|
trace_probe_clear_flag(tp, TP_FLAG_PROFILE);
|
|
|
|
if (!trace_probe_is_enabled(tp))
|
|
__disable_trace_kprobe(tp);
|
|
|
|
out:
|
|
if (file)
|
|
/*
|
|
* Synchronization is done in below function. For perf event,
|
|
* file == NULL and perf_trace_event_unreg() calls
|
|
* tracepoint_synchronize_unregister() to ensure synchronize
|
|
* event. We don't need to care about it.
|
|
*/
|
|
trace_probe_remove_file(tp, file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_DYNAMIC_FTRACE) && \
|
|
!defined(CONFIG_KPROBE_EVENTS_ON_NOTRACE)
|
|
static bool __within_notrace_func(unsigned long addr)
|
|
{
|
|
unsigned long offset, size;
|
|
|
|
if (!addr || !kallsyms_lookup_size_offset(addr, &size, &offset))
|
|
return false;
|
|
|
|
/* Get the entry address of the target function */
|
|
addr -= offset;
|
|
|
|
/*
|
|
* Since ftrace_location_range() does inclusive range check, we need
|
|
* to subtract 1 byte from the end address.
|
|
*/
|
|
return !ftrace_location_range(addr, addr + size - 1);
|
|
}
|
|
|
|
static bool within_notrace_func(struct trace_kprobe *tk)
|
|
{
|
|
unsigned long addr = trace_kprobe_address(tk);
|
|
char symname[KSYM_NAME_LEN], *p;
|
|
|
|
if (!__within_notrace_func(addr))
|
|
return false;
|
|
|
|
/* Check if the address is on a suffixed-symbol */
|
|
if (!lookup_symbol_name(addr, symname)) {
|
|
p = strchr(symname, '.');
|
|
if (!p)
|
|
return true;
|
|
*p = '\0';
|
|
addr = (unsigned long)kprobe_lookup_name(symname, 0);
|
|
if (addr)
|
|
return __within_notrace_func(addr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
#define within_notrace_func(tk) (false)
|
|
#endif
|
|
|
|
/* Internal register function - just handle k*probes and flags */
|
|
static int __register_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
int i, ret;
|
|
|
|
ret = security_locked_down(LOCKDOWN_KPROBES);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (trace_kprobe_is_registered(tk))
|
|
return -EINVAL;
|
|
|
|
if (within_notrace_func(tk)) {
|
|
pr_warn("Could not probe notrace function %s\n",
|
|
trace_kprobe_symbol(tk));
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < tk->tp.nr_args; i++) {
|
|
ret = traceprobe_update_arg(&tk->tp.args[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* Set/clear disabled flag according to tp->flag */
|
|
if (trace_probe_is_enabled(&tk->tp))
|
|
tk->rp.kp.flags &= ~KPROBE_FLAG_DISABLED;
|
|
else
|
|
tk->rp.kp.flags |= KPROBE_FLAG_DISABLED;
|
|
|
|
if (trace_kprobe_is_return(tk))
|
|
ret = register_kretprobe(&tk->rp);
|
|
else
|
|
ret = register_kprobe(&tk->rp.kp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Internal unregister function - just handle k*probes and flags */
|
|
static void __unregister_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
if (trace_kprobe_is_registered(tk)) {
|
|
if (trace_kprobe_is_return(tk))
|
|
unregister_kretprobe(&tk->rp);
|
|
else
|
|
unregister_kprobe(&tk->rp.kp);
|
|
/* Cleanup kprobe for reuse and mark it unregistered */
|
|
INIT_HLIST_NODE(&tk->rp.kp.hlist);
|
|
INIT_LIST_HEAD(&tk->rp.kp.list);
|
|
if (tk->rp.kp.symbol_name)
|
|
tk->rp.kp.addr = NULL;
|
|
}
|
|
}
|
|
|
|
/* Unregister a trace_probe and probe_event */
|
|
static int unregister_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
/* If other probes are on the event, just unregister kprobe */
|
|
if (trace_probe_has_sibling(&tk->tp))
|
|
goto unreg;
|
|
|
|
/* Enabled event can not be unregistered */
|
|
if (trace_probe_is_enabled(&tk->tp))
|
|
return -EBUSY;
|
|
|
|
/* If there's a reference to the dynamic event */
|
|
if (trace_event_dyn_busy(trace_probe_event_call(&tk->tp)))
|
|
return -EBUSY;
|
|
|
|
/* Will fail if probe is being used by ftrace or perf */
|
|
if (unregister_kprobe_event(tk))
|
|
return -EBUSY;
|
|
|
|
unreg:
|
|
__unregister_trace_kprobe(tk);
|
|
dyn_event_remove(&tk->devent);
|
|
trace_probe_unlink(&tk->tp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool trace_kprobe_has_same_kprobe(struct trace_kprobe *orig,
|
|
struct trace_kprobe *comp)
|
|
{
|
|
struct trace_probe_event *tpe = orig->tp.event;
|
|
struct trace_probe *pos;
|
|
int i;
|
|
|
|
list_for_each_entry(pos, &tpe->probes, list) {
|
|
orig = container_of(pos, struct trace_kprobe, tp);
|
|
if (strcmp(trace_kprobe_symbol(orig),
|
|
trace_kprobe_symbol(comp)) ||
|
|
trace_kprobe_offset(orig) != trace_kprobe_offset(comp))
|
|
continue;
|
|
|
|
/*
|
|
* trace_probe_compare_arg_type() ensured that nr_args and
|
|
* each argument name and type are same. Let's compare comm.
|
|
*/
|
|
for (i = 0; i < orig->tp.nr_args; i++) {
|
|
if (strcmp(orig->tp.args[i].comm,
|
|
comp->tp.args[i].comm))
|
|
break;
|
|
}
|
|
|
|
if (i == orig->tp.nr_args)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int append_trace_kprobe(struct trace_kprobe *tk, struct trace_kprobe *to)
|
|
{
|
|
int ret;
|
|
|
|
ret = trace_probe_compare_arg_type(&tk->tp, &to->tp);
|
|
if (ret) {
|
|
/* Note that argument starts index = 2 */
|
|
trace_probe_log_set_index(ret + 1);
|
|
trace_probe_log_err(0, DIFF_ARG_TYPE);
|
|
return -EEXIST;
|
|
}
|
|
if (trace_kprobe_has_same_kprobe(to, tk)) {
|
|
trace_probe_log_set_index(0);
|
|
trace_probe_log_err(0, SAME_PROBE);
|
|
return -EEXIST;
|
|
}
|
|
|
|
/* Append to existing event */
|
|
ret = trace_probe_append(&tk->tp, &to->tp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Register k*probe */
|
|
ret = __register_trace_kprobe(tk);
|
|
if (ret == -ENOENT && !trace_kprobe_module_exist(tk)) {
|
|
pr_warn("This probe might be able to register after target module is loaded. Continue.\n");
|
|
ret = 0;
|
|
}
|
|
|
|
if (ret)
|
|
trace_probe_unlink(&tk->tp);
|
|
else
|
|
dyn_event_add(&tk->devent, trace_probe_event_call(&tk->tp));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Register a trace_probe and probe_event */
|
|
static int register_trace_kprobe(struct trace_kprobe *tk)
|
|
{
|
|
struct trace_kprobe *old_tk;
|
|
int ret;
|
|
|
|
mutex_lock(&event_mutex);
|
|
|
|
old_tk = find_trace_kprobe(trace_probe_name(&tk->tp),
|
|
trace_probe_group_name(&tk->tp));
|
|
if (old_tk) {
|
|
if (trace_kprobe_is_return(tk) != trace_kprobe_is_return(old_tk)) {
|
|
trace_probe_log_set_index(0);
|
|
trace_probe_log_err(0, DIFF_PROBE_TYPE);
|
|
ret = -EEXIST;
|
|
} else {
|
|
ret = append_trace_kprobe(tk, old_tk);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
/* Register new event */
|
|
ret = register_kprobe_event(tk);
|
|
if (ret) {
|
|
if (ret == -EEXIST) {
|
|
trace_probe_log_set_index(0);
|
|
trace_probe_log_err(0, EVENT_EXIST);
|
|
} else
|
|
pr_warn("Failed to register probe event(%d)\n", ret);
|
|
goto end;
|
|
}
|
|
|
|
/* Register k*probe */
|
|
ret = __register_trace_kprobe(tk);
|
|
if (ret == -ENOENT && !trace_kprobe_module_exist(tk)) {
|
|
pr_warn("This probe might be able to register after target module is loaded. Continue.\n");
|
|
ret = 0;
|
|
}
|
|
|
|
if (ret < 0)
|
|
unregister_kprobe_event(tk);
|
|
else
|
|
dyn_event_add(&tk->devent, trace_probe_event_call(&tk->tp));
|
|
|
|
end:
|
|
mutex_unlock(&event_mutex);
|
|
return ret;
|
|
}
|
|
|
|
/* Module notifier call back, checking event on the module */
|
|
static int trace_kprobe_module_callback(struct notifier_block *nb,
|
|
unsigned long val, void *data)
|
|
{
|
|
struct module *mod = data;
|
|
struct dyn_event *pos;
|
|
struct trace_kprobe *tk;
|
|
int ret;
|
|
|
|
if (val != MODULE_STATE_COMING)
|
|
return NOTIFY_DONE;
|
|
|
|
/* Update probes on coming module */
|
|
mutex_lock(&event_mutex);
|
|
for_each_trace_kprobe(tk, pos) {
|
|
if (trace_kprobe_within_module(tk, mod)) {
|
|
/* Don't need to check busy - this should have gone. */
|
|
__unregister_trace_kprobe(tk);
|
|
ret = __register_trace_kprobe(tk);
|
|
if (ret)
|
|
pr_warn("Failed to re-register probe %s on %s: %d\n",
|
|
trace_probe_name(&tk->tp),
|
|
module_name(mod), ret);
|
|
}
|
|
}
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block trace_kprobe_module_nb = {
|
|
.notifier_call = trace_kprobe_module_callback,
|
|
.priority = 1 /* Invoked after kprobe module callback */
|
|
};
|
|
|
|
static int __trace_kprobe_create(int argc, const char *argv[])
|
|
{
|
|
/*
|
|
* Argument syntax:
|
|
* - Add kprobe:
|
|
* p[:[GRP/]EVENT] [MOD:]KSYM[+OFFS]|KADDR [FETCHARGS]
|
|
* - Add kretprobe:
|
|
* r[MAXACTIVE][:[GRP/]EVENT] [MOD:]KSYM[+0] [FETCHARGS]
|
|
* Or
|
|
* p:[GRP/]EVENT] [MOD:]KSYM[+0]%return [FETCHARGS]
|
|
*
|
|
* Fetch args:
|
|
* $retval : fetch return value
|
|
* $stack : fetch stack address
|
|
* $stackN : fetch Nth of stack (N:0-)
|
|
* $comm : fetch current task comm
|
|
* @ADDR : fetch memory at ADDR (ADDR should be in kernel)
|
|
* @SYM[+|-offs] : fetch memory at SYM +|- offs (SYM is a data symbol)
|
|
* %REG : fetch register REG
|
|
* Dereferencing memory fetch:
|
|
* +|-offs(ARG) : fetch memory at ARG +|- offs address.
|
|
* Alias name of args:
|
|
* NAME=FETCHARG : set NAME as alias of FETCHARG.
|
|
* Type of args:
|
|
* FETCHARG:TYPE : use TYPE instead of unsigned long.
|
|
*/
|
|
struct trace_kprobe *tk = NULL;
|
|
int i, len, ret = 0;
|
|
bool is_return = false;
|
|
char *symbol = NULL, *tmp = NULL;
|
|
const char *event = NULL, *group = KPROBE_EVENT_SYSTEM;
|
|
enum probe_print_type ptype;
|
|
int maxactive = 0;
|
|
long offset = 0;
|
|
void *addr = NULL;
|
|
char buf[MAX_EVENT_NAME_LEN];
|
|
unsigned int flags = TPARG_FL_KERNEL;
|
|
|
|
switch (argv[0][0]) {
|
|
case 'r':
|
|
is_return = true;
|
|
break;
|
|
case 'p':
|
|
break;
|
|
default:
|
|
return -ECANCELED;
|
|
}
|
|
if (argc < 2)
|
|
return -ECANCELED;
|
|
|
|
trace_probe_log_init("trace_kprobe", argc, argv);
|
|
|
|
event = strchr(&argv[0][1], ':');
|
|
if (event)
|
|
event++;
|
|
|
|
if (isdigit(argv[0][1])) {
|
|
if (!is_return) {
|
|
trace_probe_log_err(1, MAXACT_NO_KPROBE);
|
|
goto parse_error;
|
|
}
|
|
if (event)
|
|
len = event - &argv[0][1] - 1;
|
|
else
|
|
len = strlen(&argv[0][1]);
|
|
if (len > MAX_EVENT_NAME_LEN - 1) {
|
|
trace_probe_log_err(1, BAD_MAXACT);
|
|
goto parse_error;
|
|
}
|
|
memcpy(buf, &argv[0][1], len);
|
|
buf[len] = '\0';
|
|
ret = kstrtouint(buf, 0, &maxactive);
|
|
if (ret || !maxactive) {
|
|
trace_probe_log_err(1, BAD_MAXACT);
|
|
goto parse_error;
|
|
}
|
|
/* kretprobes instances are iterated over via a list. The
|
|
* maximum should stay reasonable.
|
|
*/
|
|
if (maxactive > KRETPROBE_MAXACTIVE_MAX) {
|
|
trace_probe_log_err(1, MAXACT_TOO_BIG);
|
|
goto parse_error;
|
|
}
|
|
}
|
|
|
|
/* try to parse an address. if that fails, try to read the
|
|
* input as a symbol. */
|
|
if (kstrtoul(argv[1], 0, (unsigned long *)&addr)) {
|
|
trace_probe_log_set_index(1);
|
|
/* Check whether uprobe event specified */
|
|
if (strchr(argv[1], '/') && strchr(argv[1], ':')) {
|
|
ret = -ECANCELED;
|
|
goto error;
|
|
}
|
|
/* a symbol specified */
|
|
symbol = kstrdup(argv[1], GFP_KERNEL);
|
|
if (!symbol)
|
|
return -ENOMEM;
|
|
|
|
tmp = strchr(symbol, '%');
|
|
if (tmp) {
|
|
if (!strcmp(tmp, "%return")) {
|
|
*tmp = '\0';
|
|
is_return = true;
|
|
} else {
|
|
trace_probe_log_err(tmp - symbol, BAD_ADDR_SUFFIX);
|
|
goto parse_error;
|
|
}
|
|
}
|
|
|
|
/* TODO: support .init module functions */
|
|
ret = traceprobe_split_symbol_offset(symbol, &offset);
|
|
if (ret || offset < 0 || offset > UINT_MAX) {
|
|
trace_probe_log_err(0, BAD_PROBE_ADDR);
|
|
goto parse_error;
|
|
}
|
|
if (is_return)
|
|
flags |= TPARG_FL_RETURN;
|
|
ret = kprobe_on_func_entry(NULL, symbol, offset);
|
|
if (ret == 0)
|
|
flags |= TPARG_FL_FENTRY;
|
|
/* Defer the ENOENT case until register kprobe */
|
|
if (ret == -EINVAL && is_return) {
|
|
trace_probe_log_err(0, BAD_RETPROBE);
|
|
goto parse_error;
|
|
}
|
|
}
|
|
|
|
trace_probe_log_set_index(0);
|
|
if (event) {
|
|
ret = traceprobe_parse_event_name(&event, &group, buf,
|
|
event - argv[0]);
|
|
if (ret)
|
|
goto parse_error;
|
|
} else {
|
|
/* Make a new event name */
|
|
if (symbol)
|
|
snprintf(buf, MAX_EVENT_NAME_LEN, "%c_%s_%ld",
|
|
is_return ? 'r' : 'p', symbol, offset);
|
|
else
|
|
snprintf(buf, MAX_EVENT_NAME_LEN, "%c_0x%p",
|
|
is_return ? 'r' : 'p', addr);
|
|
sanitize_event_name(buf);
|
|
event = buf;
|
|
}
|
|
|
|
/* setup a probe */
|
|
tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive,
|
|
argc - 2, is_return);
|
|
if (IS_ERR(tk)) {
|
|
ret = PTR_ERR(tk);
|
|
/* This must return -ENOMEM, else there is a bug */
|
|
WARN_ON_ONCE(ret != -ENOMEM);
|
|
goto out; /* We know tk is not allocated */
|
|
}
|
|
argc -= 2; argv += 2;
|
|
|
|
/* parse arguments */
|
|
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
|
|
trace_probe_log_set_index(i + 2);
|
|
ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], flags);
|
|
if (ret)
|
|
goto error; /* This can be -ENOMEM */
|
|
}
|
|
|
|
ptype = is_return ? PROBE_PRINT_RETURN : PROBE_PRINT_NORMAL;
|
|
ret = traceprobe_set_print_fmt(&tk->tp, ptype);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
ret = register_trace_kprobe(tk);
|
|
if (ret) {
|
|
trace_probe_log_set_index(1);
|
|
if (ret == -EILSEQ)
|
|
trace_probe_log_err(0, BAD_INSN_BNDRY);
|
|
else if (ret == -ENOENT)
|
|
trace_probe_log_err(0, BAD_PROBE_ADDR);
|
|
else if (ret != -ENOMEM && ret != -EEXIST)
|
|
trace_probe_log_err(0, FAIL_REG_PROBE);
|
|
goto error;
|
|
}
|
|
|
|
out:
|
|
trace_probe_log_clear();
|
|
kfree(symbol);
|
|
return ret;
|
|
|
|
parse_error:
|
|
ret = -EINVAL;
|
|
error:
|
|
free_trace_kprobe(tk);
|
|
goto out;
|
|
}
|
|
|
|
static int trace_kprobe_create(const char *raw_command)
|
|
{
|
|
return trace_probe_create(raw_command, __trace_kprobe_create);
|
|
}
|
|
|
|
static int create_or_delete_trace_kprobe(const char *raw_command)
|
|
{
|
|
int ret;
|
|
|
|
if (raw_command[0] == '-')
|
|
return dyn_event_release(raw_command, &trace_kprobe_ops);
|
|
|
|
ret = trace_kprobe_create(raw_command);
|
|
return ret == -ECANCELED ? -EINVAL : ret;
|
|
}
|
|
|
|
static int trace_kprobe_run_command(struct dynevent_cmd *cmd)
|
|
{
|
|
return create_or_delete_trace_kprobe(cmd->seq.buffer);
|
|
}
|
|
|
|
/**
|
|
* kprobe_event_cmd_init - Initialize a kprobe event command object
|
|
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
|
* @buf: A pointer to the buffer used to build the command
|
|
* @maxlen: The length of the buffer passed in @buf
|
|
*
|
|
* Initialize a synthetic event command object. Use this before
|
|
* calling any of the other kprobe_event functions.
|
|
*/
|
|
void kprobe_event_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen)
|
|
{
|
|
dynevent_cmd_init(cmd, buf, maxlen, DYNEVENT_TYPE_KPROBE,
|
|
trace_kprobe_run_command);
|
|
}
|
|
EXPORT_SYMBOL_GPL(kprobe_event_cmd_init);
|
|
|
|
/**
|
|
* __kprobe_event_gen_cmd_start - Generate a kprobe event command from arg list
|
|
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
|
* @name: The name of the kprobe event
|
|
* @loc: The location of the kprobe event
|
|
* @kretprobe: Is this a return probe?
|
|
* @args: Variable number of arg (pairs), one pair for each field
|
|
*
|
|
* NOTE: Users normally won't want to call this function directly, but
|
|
* rather use the kprobe_event_gen_cmd_start() wrapper, which automatically
|
|
* adds a NULL to the end of the arg list. If this function is used
|
|
* directly, make sure the last arg in the variable arg list is NULL.
|
|
*
|
|
* Generate a kprobe event command to be executed by
|
|
* kprobe_event_gen_cmd_end(). This function can be used to generate the
|
|
* complete command or only the first part of it; in the latter case,
|
|
* kprobe_event_add_fields() can be used to add more fields following this.
|
|
*
|
|
* Unlikely the synth_event_gen_cmd_start(), @loc must be specified. This
|
|
* returns -EINVAL if @loc == NULL.
|
|
*
|
|
* Return: 0 if successful, error otherwise.
|
|
*/
|
|
int __kprobe_event_gen_cmd_start(struct dynevent_cmd *cmd, bool kretprobe,
|
|
const char *name, const char *loc, ...)
|
|
{
|
|
char buf[MAX_EVENT_NAME_LEN];
|
|
struct dynevent_arg arg;
|
|
va_list args;
|
|
int ret;
|
|
|
|
if (cmd->type != DYNEVENT_TYPE_KPROBE)
|
|
return -EINVAL;
|
|
|
|
if (!loc)
|
|
return -EINVAL;
|
|
|
|
if (kretprobe)
|
|
snprintf(buf, MAX_EVENT_NAME_LEN, "r:kprobes/%s", name);
|
|
else
|
|
snprintf(buf, MAX_EVENT_NAME_LEN, "p:kprobes/%s", name);
|
|
|
|
ret = dynevent_str_add(cmd, buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dynevent_arg_init(&arg, 0);
|
|
arg.str = loc;
|
|
ret = dynevent_arg_add(cmd, &arg, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
va_start(args, loc);
|
|
for (;;) {
|
|
const char *field;
|
|
|
|
field = va_arg(args, const char *);
|
|
if (!field)
|
|
break;
|
|
|
|
if (++cmd->n_fields > MAX_TRACE_ARGS) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
arg.str = field;
|
|
ret = dynevent_arg_add(cmd, &arg, NULL);
|
|
if (ret)
|
|
break;
|
|
}
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__kprobe_event_gen_cmd_start);
|
|
|
|
/**
|
|
* __kprobe_event_add_fields - Add probe fields to a kprobe command from arg list
|
|
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
|
* @args: Variable number of arg (pairs), one pair for each field
|
|
*
|
|
* NOTE: Users normally won't want to call this function directly, but
|
|
* rather use the kprobe_event_add_fields() wrapper, which
|
|
* automatically adds a NULL to the end of the arg list. If this
|
|
* function is used directly, make sure the last arg in the variable
|
|
* arg list is NULL.
|
|
*
|
|
* Add probe fields to an existing kprobe command using a variable
|
|
* list of args. Fields are added in the same order they're listed.
|
|
*
|
|
* Return: 0 if successful, error otherwise.
|
|
*/
|
|
int __kprobe_event_add_fields(struct dynevent_cmd *cmd, ...)
|
|
{
|
|
struct dynevent_arg arg;
|
|
va_list args;
|
|
int ret = 0;
|
|
|
|
if (cmd->type != DYNEVENT_TYPE_KPROBE)
|
|
return -EINVAL;
|
|
|
|
dynevent_arg_init(&arg, 0);
|
|
|
|
va_start(args, cmd);
|
|
for (;;) {
|
|
const char *field;
|
|
|
|
field = va_arg(args, const char *);
|
|
if (!field)
|
|
break;
|
|
|
|
if (++cmd->n_fields > MAX_TRACE_ARGS) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
arg.str = field;
|
|
ret = dynevent_arg_add(cmd, &arg, NULL);
|
|
if (ret)
|
|
break;
|
|
}
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__kprobe_event_add_fields);
|
|
|
|
/**
|
|
* kprobe_event_delete - Delete a kprobe event
|
|
* @name: The name of the kprobe event to delete
|
|
*
|
|
* Delete a kprobe event with the give @name from kernel code rather
|
|
* than directly from the command line.
|
|
*
|
|
* Return: 0 if successful, error otherwise.
|
|
*/
|
|
int kprobe_event_delete(const char *name)
|
|
{
|
|
char buf[MAX_EVENT_NAME_LEN];
|
|
|
|
snprintf(buf, MAX_EVENT_NAME_LEN, "-:%s", name);
|
|
|
|
return create_or_delete_trace_kprobe(buf);
|
|
}
|
|
EXPORT_SYMBOL_GPL(kprobe_event_delete);
|
|
|
|
static int trace_kprobe_release(struct dyn_event *ev)
|
|
{
|
|
struct trace_kprobe *tk = to_trace_kprobe(ev);
|
|
int ret = unregister_trace_kprobe(tk);
|
|
|
|
if (!ret)
|
|
free_trace_kprobe(tk);
|
|
return ret;
|
|
}
|
|
|
|
static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev)
|
|
{
|
|
struct trace_kprobe *tk = to_trace_kprobe(ev);
|
|
int i;
|
|
|
|
seq_putc(m, trace_kprobe_is_return(tk) ? 'r' : 'p');
|
|
if (trace_kprobe_is_return(tk) && tk->rp.maxactive)
|
|
seq_printf(m, "%d", tk->rp.maxactive);
|
|
seq_printf(m, ":%s/%s", trace_probe_group_name(&tk->tp),
|
|
trace_probe_name(&tk->tp));
|
|
|
|
if (!tk->symbol)
|
|
seq_printf(m, " 0x%p", tk->rp.kp.addr);
|
|
else if (tk->rp.kp.offset)
|
|
seq_printf(m, " %s+%u", trace_kprobe_symbol(tk),
|
|
tk->rp.kp.offset);
|
|
else
|
|
seq_printf(m, " %s", trace_kprobe_symbol(tk));
|
|
|
|
for (i = 0; i < tk->tp.nr_args; i++)
|
|
seq_printf(m, " %s=%s", tk->tp.args[i].name, tk->tp.args[i].comm);
|
|
seq_putc(m, '\n');
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int probes_seq_show(struct seq_file *m, void *v)
|
|
{
|
|
struct dyn_event *ev = v;
|
|
|
|
if (!is_trace_kprobe(ev))
|
|
return 0;
|
|
|
|
return trace_kprobe_show(m, ev);
|
|
}
|
|
|
|
static const struct seq_operations probes_seq_op = {
|
|
.start = dyn_event_seq_start,
|
|
.next = dyn_event_seq_next,
|
|
.stop = dyn_event_seq_stop,
|
|
.show = probes_seq_show
|
|
};
|
|
|
|
static int probes_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = security_locked_down(LOCKDOWN_TRACEFS);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC)) {
|
|
ret = dyn_events_release_all(&trace_kprobe_ops);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return seq_open(file, &probes_seq_op);
|
|
}
|
|
|
|
static ssize_t probes_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return trace_parse_run_command(file, buffer, count, ppos,
|
|
create_or_delete_trace_kprobe);
|
|
}
|
|
|
|
static const struct file_operations kprobe_events_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = probes_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
.write = probes_write,
|
|
};
|
|
|
|
/* Probes profiling interfaces */
|
|
static int probes_profile_seq_show(struct seq_file *m, void *v)
|
|
{
|
|
struct dyn_event *ev = v;
|
|
struct trace_kprobe *tk;
|
|
unsigned long nmissed;
|
|
|
|
if (!is_trace_kprobe(ev))
|
|
return 0;
|
|
|
|
tk = to_trace_kprobe(ev);
|
|
nmissed = trace_kprobe_is_return(tk) ?
|
|
tk->rp.kp.nmissed + tk->rp.nmissed : tk->rp.kp.nmissed;
|
|
seq_printf(m, " %-44s %15lu %15lu\n",
|
|
trace_probe_name(&tk->tp),
|
|
trace_kprobe_nhit(tk),
|
|
nmissed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations profile_seq_op = {
|
|
.start = dyn_event_seq_start,
|
|
.next = dyn_event_seq_next,
|
|
.stop = dyn_event_seq_stop,
|
|
.show = probes_profile_seq_show
|
|
};
|
|
|
|
static int profile_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = security_locked_down(LOCKDOWN_TRACEFS);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return seq_open(file, &profile_seq_op);
|
|
}
|
|
|
|
static const struct file_operations kprobe_profile_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = profile_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/* Kprobe specific fetch functions */
|
|
|
|
/* Return the length of string -- including null terminal byte */
|
|
static nokprobe_inline int
|
|
fetch_store_strlen_user(unsigned long addr)
|
|
{
|
|
return kern_fetch_store_strlen_user(addr);
|
|
}
|
|
|
|
/* Return the length of string -- including null terminal byte */
|
|
static nokprobe_inline int
|
|
fetch_store_strlen(unsigned long addr)
|
|
{
|
|
return kern_fetch_store_strlen(addr);
|
|
}
|
|
|
|
/*
|
|
* Fetch a null-terminated string from user. Caller MUST set *(u32 *)buf
|
|
* with max length and relative data location.
|
|
*/
|
|
static nokprobe_inline int
|
|
fetch_store_string_user(unsigned long addr, void *dest, void *base)
|
|
{
|
|
return kern_fetch_store_string_user(addr, dest, base);
|
|
}
|
|
|
|
/*
|
|
* Fetch a null-terminated string. Caller MUST set *(u32 *)buf with max
|
|
* length and relative data location.
|
|
*/
|
|
static nokprobe_inline int
|
|
fetch_store_string(unsigned long addr, void *dest, void *base)
|
|
{
|
|
return kern_fetch_store_string(addr, dest, base);
|
|
}
|
|
|
|
static nokprobe_inline int
|
|
probe_mem_read_user(void *dest, void *src, size_t size)
|
|
{
|
|
const void __user *uaddr = (__force const void __user *)src;
|
|
|
|
return copy_from_user_nofault(dest, uaddr, size);
|
|
}
|
|
|
|
static nokprobe_inline int
|
|
probe_mem_read(void *dest, void *src, size_t size)
|
|
{
|
|
#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
|
|
if ((unsigned long)src < TASK_SIZE)
|
|
return probe_mem_read_user(dest, src, size);
|
|
#endif
|
|
return copy_from_kernel_nofault(dest, src, size);
|
|
}
|
|
|
|
/* Note that we don't verify it, since the code does not come from user space */
|
|
static int
|
|
process_fetch_insn(struct fetch_insn *code, void *rec, void *dest,
|
|
void *base)
|
|
{
|
|
struct pt_regs *regs = rec;
|
|
unsigned long val;
|
|
|
|
retry:
|
|
/* 1st stage: get value from context */
|
|
switch (code->op) {
|
|
case FETCH_OP_REG:
|
|
val = regs_get_register(regs, code->param);
|
|
break;
|
|
case FETCH_OP_STACK:
|
|
val = regs_get_kernel_stack_nth(regs, code->param);
|
|
break;
|
|
case FETCH_OP_STACKP:
|
|
val = kernel_stack_pointer(regs);
|
|
break;
|
|
case FETCH_OP_RETVAL:
|
|
val = regs_return_value(regs);
|
|
break;
|
|
case FETCH_OP_IMM:
|
|
val = code->immediate;
|
|
break;
|
|
case FETCH_OP_COMM:
|
|
val = (unsigned long)current->comm;
|
|
break;
|
|
case FETCH_OP_DATA:
|
|
val = (unsigned long)code->data;
|
|
break;
|
|
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
|
case FETCH_OP_ARG:
|
|
val = regs_get_kernel_argument(regs, code->param);
|
|
break;
|
|
#endif
|
|
case FETCH_NOP_SYMBOL: /* Ignore a place holder */
|
|
code++;
|
|
goto retry;
|
|
default:
|
|
return -EILSEQ;
|
|
}
|
|
code++;
|
|
|
|
return process_fetch_insn_bottom(code, val, dest, base);
|
|
}
|
|
NOKPROBE_SYMBOL(process_fetch_insn)
|
|
|
|
/* Kprobe handler */
|
|
static nokprobe_inline void
|
|
__kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs,
|
|
struct trace_event_file *trace_file)
|
|
{
|
|
struct kprobe_trace_entry_head *entry;
|
|
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
|
struct trace_event_buffer fbuffer;
|
|
int dsize;
|
|
|
|
WARN_ON(call != trace_file->event_call);
|
|
|
|
if (trace_trigger_soft_disabled(trace_file))
|
|
return;
|
|
|
|
fbuffer.trace_ctx = tracing_gen_ctx();
|
|
fbuffer.trace_file = trace_file;
|
|
|
|
dsize = __get_data_size(&tk->tp, regs);
|
|
|
|
fbuffer.event =
|
|
trace_event_buffer_lock_reserve(&fbuffer.buffer, trace_file,
|
|
call->event.type,
|
|
sizeof(*entry) + tk->tp.size + dsize,
|
|
fbuffer.trace_ctx);
|
|
if (!fbuffer.event)
|
|
return;
|
|
|
|
fbuffer.regs = regs;
|
|
entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event);
|
|
entry->ip = (unsigned long)tk->rp.kp.addr;
|
|
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
|
|
|
trace_event_buffer_commit(&fbuffer);
|
|
}
|
|
|
|
static void
|
|
kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs)
|
|
{
|
|
struct event_file_link *link;
|
|
|
|
trace_probe_for_each_link_rcu(link, &tk->tp)
|
|
__kprobe_trace_func(tk, regs, link->file);
|
|
}
|
|
NOKPROBE_SYMBOL(kprobe_trace_func);
|
|
|
|
/* Kretprobe handler */
|
|
static nokprobe_inline void
|
|
__kretprobe_trace_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
|
|
struct pt_regs *regs,
|
|
struct trace_event_file *trace_file)
|
|
{
|
|
struct kretprobe_trace_entry_head *entry;
|
|
struct trace_event_buffer fbuffer;
|
|
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
|
int dsize;
|
|
|
|
WARN_ON(call != trace_file->event_call);
|
|
|
|
if (trace_trigger_soft_disabled(trace_file))
|
|
return;
|
|
|
|
fbuffer.trace_ctx = tracing_gen_ctx();
|
|
fbuffer.trace_file = trace_file;
|
|
|
|
dsize = __get_data_size(&tk->tp, regs);
|
|
fbuffer.event =
|
|
trace_event_buffer_lock_reserve(&fbuffer.buffer, trace_file,
|
|
call->event.type,
|
|
sizeof(*entry) + tk->tp.size + dsize,
|
|
fbuffer.trace_ctx);
|
|
if (!fbuffer.event)
|
|
return;
|
|
|
|
fbuffer.regs = regs;
|
|
entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event);
|
|
entry->func = (unsigned long)tk->rp.kp.addr;
|
|
entry->ret_ip = (unsigned long)ri->ret_addr;
|
|
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
|
|
|
trace_event_buffer_commit(&fbuffer);
|
|
}
|
|
|
|
static void
|
|
kretprobe_trace_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct event_file_link *link;
|
|
|
|
trace_probe_for_each_link_rcu(link, &tk->tp)
|
|
__kretprobe_trace_func(tk, ri, regs, link->file);
|
|
}
|
|
NOKPROBE_SYMBOL(kretprobe_trace_func);
|
|
|
|
/* Event entry printers */
|
|
static enum print_line_t
|
|
print_kprobe_event(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct kprobe_trace_entry_head *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct trace_probe *tp;
|
|
|
|
field = (struct kprobe_trace_entry_head *)iter->ent;
|
|
tp = trace_probe_primary_from_call(
|
|
container_of(event, struct trace_event_call, event));
|
|
if (WARN_ON_ONCE(!tp))
|
|
goto out;
|
|
|
|
trace_seq_printf(s, "%s: (", trace_probe_name(tp));
|
|
|
|
if (!seq_print_ip_sym(s, field->ip, flags | TRACE_ITER_SYM_OFFSET))
|
|
goto out;
|
|
|
|
trace_seq_putc(s, ')');
|
|
|
|
if (print_probe_args(s, tp->args, tp->nr_args,
|
|
(u8 *)&field[1], field) < 0)
|
|
goto out;
|
|
|
|
trace_seq_putc(s, '\n');
|
|
out:
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t
|
|
print_kretprobe_event(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct kretprobe_trace_entry_head *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct trace_probe *tp;
|
|
|
|
field = (struct kretprobe_trace_entry_head *)iter->ent;
|
|
tp = trace_probe_primary_from_call(
|
|
container_of(event, struct trace_event_call, event));
|
|
if (WARN_ON_ONCE(!tp))
|
|
goto out;
|
|
|
|
trace_seq_printf(s, "%s: (", trace_probe_name(tp));
|
|
|
|
if (!seq_print_ip_sym(s, field->ret_ip, flags | TRACE_ITER_SYM_OFFSET))
|
|
goto out;
|
|
|
|
trace_seq_puts(s, " <- ");
|
|
|
|
if (!seq_print_ip_sym(s, field->func, flags & ~TRACE_ITER_SYM_OFFSET))
|
|
goto out;
|
|
|
|
trace_seq_putc(s, ')');
|
|
|
|
if (print_probe_args(s, tp->args, tp->nr_args,
|
|
(u8 *)&field[1], field) < 0)
|
|
goto out;
|
|
|
|
trace_seq_putc(s, '\n');
|
|
|
|
out:
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
|
|
static int kprobe_event_define_fields(struct trace_event_call *event_call)
|
|
{
|
|
int ret;
|
|
struct kprobe_trace_entry_head field;
|
|
struct trace_probe *tp;
|
|
|
|
tp = trace_probe_primary_from_call(event_call);
|
|
if (WARN_ON_ONCE(!tp))
|
|
return -ENOENT;
|
|
|
|
DEFINE_FIELD(unsigned long, ip, FIELD_STRING_IP, 0);
|
|
|
|
return traceprobe_define_arg_fields(event_call, sizeof(field), tp);
|
|
}
|
|
|
|
static int kretprobe_event_define_fields(struct trace_event_call *event_call)
|
|
{
|
|
int ret;
|
|
struct kretprobe_trace_entry_head field;
|
|
struct trace_probe *tp;
|
|
|
|
tp = trace_probe_primary_from_call(event_call);
|
|
if (WARN_ON_ONCE(!tp))
|
|
return -ENOENT;
|
|
|
|
DEFINE_FIELD(unsigned long, func, FIELD_STRING_FUNC, 0);
|
|
DEFINE_FIELD(unsigned long, ret_ip, FIELD_STRING_RETIP, 0);
|
|
|
|
return traceprobe_define_arg_fields(event_call, sizeof(field), tp);
|
|
}
|
|
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
|
|
/* Kprobe profile handler */
|
|
static int
|
|
kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
|
|
{
|
|
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
|
struct kprobe_trace_entry_head *entry;
|
|
struct hlist_head *head;
|
|
int size, __size, dsize;
|
|
int rctx;
|
|
|
|
if (bpf_prog_array_valid(call)) {
|
|
unsigned long orig_ip = instruction_pointer(regs);
|
|
int ret;
|
|
|
|
ret = trace_call_bpf(call, regs);
|
|
|
|
/*
|
|
* We need to check and see if we modified the pc of the
|
|
* pt_regs, and if so return 1 so that we don't do the
|
|
* single stepping.
|
|
*/
|
|
if (orig_ip != instruction_pointer(regs))
|
|
return 1;
|
|
if (!ret)
|
|
return 0;
|
|
}
|
|
|
|
head = this_cpu_ptr(call->perf_events);
|
|
if (hlist_empty(head))
|
|
return 0;
|
|
|
|
dsize = __get_data_size(&tk->tp, regs);
|
|
__size = sizeof(*entry) + tk->tp.size + dsize;
|
|
size = ALIGN(__size + sizeof(u32), sizeof(u64));
|
|
size -= sizeof(u32);
|
|
|
|
entry = perf_trace_buf_alloc(size, NULL, &rctx);
|
|
if (!entry)
|
|
return 0;
|
|
|
|
entry->ip = (unsigned long)tk->rp.kp.addr;
|
|
memset(&entry[1], 0, dsize);
|
|
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
|
perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs,
|
|
head, NULL);
|
|
return 0;
|
|
}
|
|
NOKPROBE_SYMBOL(kprobe_perf_func);
|
|
|
|
/* Kretprobe profile handler */
|
|
static void
|
|
kretprobe_perf_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
|
struct kretprobe_trace_entry_head *entry;
|
|
struct hlist_head *head;
|
|
int size, __size, dsize;
|
|
int rctx;
|
|
|
|
if (bpf_prog_array_valid(call) && !trace_call_bpf(call, regs))
|
|
return;
|
|
|
|
head = this_cpu_ptr(call->perf_events);
|
|
if (hlist_empty(head))
|
|
return;
|
|
|
|
dsize = __get_data_size(&tk->tp, regs);
|
|
__size = sizeof(*entry) + tk->tp.size + dsize;
|
|
size = ALIGN(__size + sizeof(u32), sizeof(u64));
|
|
size -= sizeof(u32);
|
|
|
|
entry = perf_trace_buf_alloc(size, NULL, &rctx);
|
|
if (!entry)
|
|
return;
|
|
|
|
entry->func = (unsigned long)tk->rp.kp.addr;
|
|
entry->ret_ip = (unsigned long)ri->ret_addr;
|
|
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
|
perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs,
|
|
head, NULL);
|
|
}
|
|
NOKPROBE_SYMBOL(kretprobe_perf_func);
|
|
|
|
int bpf_get_kprobe_info(const struct perf_event *event, u32 *fd_type,
|
|
const char **symbol, u64 *probe_offset,
|
|
u64 *probe_addr, bool perf_type_tracepoint)
|
|
{
|
|
const char *pevent = trace_event_name(event->tp_event);
|
|
const char *group = event->tp_event->class->system;
|
|
struct trace_kprobe *tk;
|
|
|
|
if (perf_type_tracepoint)
|
|
tk = find_trace_kprobe(pevent, group);
|
|
else
|
|
tk = trace_kprobe_primary_from_call(event->tp_event);
|
|
if (!tk)
|
|
return -EINVAL;
|
|
|
|
*fd_type = trace_kprobe_is_return(tk) ? BPF_FD_TYPE_KRETPROBE
|
|
: BPF_FD_TYPE_KPROBE;
|
|
if (tk->symbol) {
|
|
*symbol = tk->symbol;
|
|
*probe_offset = tk->rp.kp.offset;
|
|
*probe_addr = 0;
|
|
} else {
|
|
*symbol = NULL;
|
|
*probe_offset = 0;
|
|
*probe_addr = (unsigned long)tk->rp.kp.addr;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PERF_EVENTS */
|
|
|
|
/*
|
|
* called by perf_trace_init() or __ftrace_set_clr_event() under event_mutex.
|
|
*
|
|
* kprobe_trace_self_tests_init() does enable_trace_probe/disable_trace_probe
|
|
* lockless, but we can't race with this __init function.
|
|
*/
|
|
static int kprobe_register(struct trace_event_call *event,
|
|
enum trace_reg type, void *data)
|
|
{
|
|
struct trace_event_file *file = data;
|
|
|
|
switch (type) {
|
|
case TRACE_REG_REGISTER:
|
|
return enable_trace_kprobe(event, file);
|
|
case TRACE_REG_UNREGISTER:
|
|
return disable_trace_kprobe(event, file);
|
|
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
case TRACE_REG_PERF_REGISTER:
|
|
return enable_trace_kprobe(event, NULL);
|
|
case TRACE_REG_PERF_UNREGISTER:
|
|
return disable_trace_kprobe(event, NULL);
|
|
case TRACE_REG_PERF_OPEN:
|
|
case TRACE_REG_PERF_CLOSE:
|
|
case TRACE_REG_PERF_ADD:
|
|
case TRACE_REG_PERF_DEL:
|
|
return 0;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs)
|
|
{
|
|
struct trace_kprobe *tk = container_of(kp, struct trace_kprobe, rp.kp);
|
|
int ret = 0;
|
|
|
|
raw_cpu_inc(*tk->nhit);
|
|
|
|
if (trace_probe_test_flag(&tk->tp, TP_FLAG_TRACE))
|
|
kprobe_trace_func(tk, regs);
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
if (trace_probe_test_flag(&tk->tp, TP_FLAG_PROFILE))
|
|
ret = kprobe_perf_func(tk, regs);
|
|
#endif
|
|
return ret;
|
|
}
|
|
NOKPROBE_SYMBOL(kprobe_dispatcher);
|
|
|
|
static int
|
|
kretprobe_dispatcher(struct kretprobe_instance *ri, struct pt_regs *regs)
|
|
{
|
|
struct kretprobe *rp = get_kretprobe(ri);
|
|
struct trace_kprobe *tk;
|
|
|
|
/*
|
|
* There is a small chance that get_kretprobe(ri) returns NULL when
|
|
* the kretprobe is unregister on another CPU between kretprobe's
|
|
* trampoline_handler and this function.
|
|
*/
|
|
if (unlikely(!rp))
|
|
return 0;
|
|
|
|
tk = container_of(rp, struct trace_kprobe, rp);
|
|
raw_cpu_inc(*tk->nhit);
|
|
|
|
if (trace_probe_test_flag(&tk->tp, TP_FLAG_TRACE))
|
|
kretprobe_trace_func(tk, ri, regs);
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
if (trace_probe_test_flag(&tk->tp, TP_FLAG_PROFILE))
|
|
kretprobe_perf_func(tk, ri, regs);
|
|
#endif
|
|
return 0; /* We don't tweak kernel, so just return 0 */
|
|
}
|
|
NOKPROBE_SYMBOL(kretprobe_dispatcher);
|
|
|
|
static struct trace_event_functions kretprobe_funcs = {
|
|
.trace = print_kretprobe_event
|
|
};
|
|
|
|
static struct trace_event_functions kprobe_funcs = {
|
|
.trace = print_kprobe_event
|
|
};
|
|
|
|
static struct trace_event_fields kretprobe_fields_array[] = {
|
|
{ .type = TRACE_FUNCTION_TYPE,
|
|
.define_fields = kretprobe_event_define_fields },
|
|
{}
|
|
};
|
|
|
|
static struct trace_event_fields kprobe_fields_array[] = {
|
|
{ .type = TRACE_FUNCTION_TYPE,
|
|
.define_fields = kprobe_event_define_fields },
|
|
{}
|
|
};
|
|
|
|
static inline void init_trace_event_call(struct trace_kprobe *tk)
|
|
{
|
|
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
|
|
|
if (trace_kprobe_is_return(tk)) {
|
|
call->event.funcs = &kretprobe_funcs;
|
|
call->class->fields_array = kretprobe_fields_array;
|
|
} else {
|
|
call->event.funcs = &kprobe_funcs;
|
|
call->class->fields_array = kprobe_fields_array;
|
|
}
|
|
|
|
call->flags = TRACE_EVENT_FL_KPROBE;
|
|
call->class->reg = kprobe_register;
|
|
}
|
|
|
|
static int register_kprobe_event(struct trace_kprobe *tk)
|
|
{
|
|
init_trace_event_call(tk);
|
|
|
|
return trace_probe_register_event_call(&tk->tp);
|
|
}
|
|
|
|
static int unregister_kprobe_event(struct trace_kprobe *tk)
|
|
{
|
|
return trace_probe_unregister_event_call(&tk->tp);
|
|
}
|
|
|
|
#ifdef CONFIG_PERF_EVENTS
|
|
/* create a trace_kprobe, but don't add it to global lists */
|
|
struct trace_event_call *
|
|
create_local_trace_kprobe(char *func, void *addr, unsigned long offs,
|
|
bool is_return)
|
|
{
|
|
enum probe_print_type ptype;
|
|
struct trace_kprobe *tk;
|
|
int ret;
|
|
char *event;
|
|
|
|
/*
|
|
* local trace_kprobes are not added to dyn_event, so they are never
|
|
* searched in find_trace_kprobe(). Therefore, there is no concern of
|
|
* duplicated name here.
|
|
*/
|
|
event = func ? func : "DUMMY_EVENT";
|
|
|
|
tk = alloc_trace_kprobe(KPROBE_EVENT_SYSTEM, event, (void *)addr, func,
|
|
offs, 0 /* maxactive */, 0 /* nargs */,
|
|
is_return);
|
|
|
|
if (IS_ERR(tk)) {
|
|
pr_info("Failed to allocate trace_probe.(%d)\n",
|
|
(int)PTR_ERR(tk));
|
|
return ERR_CAST(tk);
|
|
}
|
|
|
|
init_trace_event_call(tk);
|
|
|
|
ptype = trace_kprobe_is_return(tk) ?
|
|
PROBE_PRINT_RETURN : PROBE_PRINT_NORMAL;
|
|
if (traceprobe_set_print_fmt(&tk->tp, ptype) < 0) {
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
ret = __register_trace_kprobe(tk);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
return trace_probe_event_call(&tk->tp);
|
|
error:
|
|
free_trace_kprobe(tk);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
void destroy_local_trace_kprobe(struct trace_event_call *event_call)
|
|
{
|
|
struct trace_kprobe *tk;
|
|
|
|
tk = trace_kprobe_primary_from_call(event_call);
|
|
if (unlikely(!tk))
|
|
return;
|
|
|
|
if (trace_probe_is_enabled(&tk->tp)) {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
|
|
__unregister_trace_kprobe(tk);
|
|
|
|
free_trace_kprobe(tk);
|
|
}
|
|
#endif /* CONFIG_PERF_EVENTS */
|
|
|
|
static __init void enable_boot_kprobe_events(void)
|
|
{
|
|
struct trace_array *tr = top_trace_array();
|
|
struct trace_event_file *file;
|
|
struct trace_kprobe *tk;
|
|
struct dyn_event *pos;
|
|
|
|
mutex_lock(&event_mutex);
|
|
for_each_trace_kprobe(tk, pos) {
|
|
list_for_each_entry(file, &tr->events, list)
|
|
if (file->event_call == trace_probe_event_call(&tk->tp))
|
|
trace_event_enable_disable(file, 1, 0);
|
|
}
|
|
mutex_unlock(&event_mutex);
|
|
}
|
|
|
|
static __init void setup_boot_kprobe_events(void)
|
|
{
|
|
char *p, *cmd = kprobe_boot_events_buf;
|
|
int ret;
|
|
|
|
strreplace(kprobe_boot_events_buf, ',', ' ');
|
|
|
|
while (cmd && *cmd != '\0') {
|
|
p = strchr(cmd, ';');
|
|
if (p)
|
|
*p++ = '\0';
|
|
|
|
ret = create_or_delete_trace_kprobe(cmd);
|
|
if (ret)
|
|
pr_warn("Failed to add event(%d): %s\n", ret, cmd);
|
|
|
|
cmd = p;
|
|
}
|
|
|
|
enable_boot_kprobe_events();
|
|
}
|
|
|
|
/*
|
|
* Register dynevent at core_initcall. This allows kernel to setup kprobe
|
|
* events in postcore_initcall without tracefs.
|
|
*/
|
|
static __init int init_kprobe_trace_early(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = dyn_event_register(&trace_kprobe_ops);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (register_module_notifier(&trace_kprobe_module_nb))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
core_initcall(init_kprobe_trace_early);
|
|
|
|
/* Make a tracefs interface for controlling probe points */
|
|
static __init int init_kprobe_trace(void)
|
|
{
|
|
int ret;
|
|
struct dentry *entry;
|
|
|
|
ret = tracing_init_dentry();
|
|
if (ret)
|
|
return 0;
|
|
|
|
entry = tracefs_create_file("kprobe_events", 0644, NULL,
|
|
NULL, &kprobe_events_ops);
|
|
|
|
/* Event list interface */
|
|
if (!entry)
|
|
pr_warn("Could not create tracefs 'kprobe_events' entry\n");
|
|
|
|
/* Profile interface */
|
|
entry = tracefs_create_file("kprobe_profile", 0444, NULL,
|
|
NULL, &kprobe_profile_ops);
|
|
|
|
if (!entry)
|
|
pr_warn("Could not create tracefs 'kprobe_profile' entry\n");
|
|
|
|
setup_boot_kprobe_events();
|
|
|
|
return 0;
|
|
}
|
|
fs_initcall(init_kprobe_trace);
|
|
|
|
|
|
#ifdef CONFIG_FTRACE_STARTUP_TEST
|
|
static __init struct trace_event_file *
|
|
find_trace_probe_file(struct trace_kprobe *tk, struct trace_array *tr)
|
|
{
|
|
struct trace_event_file *file;
|
|
|
|
list_for_each_entry(file, &tr->events, list)
|
|
if (file->event_call == trace_probe_event_call(&tk->tp))
|
|
return file;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Nobody but us can call enable_trace_kprobe/disable_trace_kprobe at this
|
|
* stage, we can do this lockless.
|
|
*/
|
|
static __init int kprobe_trace_self_tests_init(void)
|
|
{
|
|
int ret, warn = 0;
|
|
int (*target)(int, int, int, int, int, int);
|
|
struct trace_kprobe *tk;
|
|
struct trace_event_file *file;
|
|
|
|
if (tracing_is_disabled())
|
|
return -ENODEV;
|
|
|
|
if (tracing_selftest_disabled)
|
|
return 0;
|
|
|
|
target = kprobe_trace_selftest_target;
|
|
|
|
pr_info("Testing kprobe tracing: ");
|
|
|
|
ret = create_or_delete_trace_kprobe("p:testprobe kprobe_trace_selftest_target $stack $stack0 +0($stack)");
|
|
if (WARN_ON_ONCE(ret)) {
|
|
pr_warn("error on probing function entry.\n");
|
|
warn++;
|
|
} else {
|
|
/* Enable trace point */
|
|
tk = find_trace_kprobe("testprobe", KPROBE_EVENT_SYSTEM);
|
|
if (WARN_ON_ONCE(tk == NULL)) {
|
|
pr_warn("error on getting new probe.\n");
|
|
warn++;
|
|
} else {
|
|
file = find_trace_probe_file(tk, top_trace_array());
|
|
if (WARN_ON_ONCE(file == NULL)) {
|
|
pr_warn("error on getting probe file.\n");
|
|
warn++;
|
|
} else
|
|
enable_trace_kprobe(
|
|
trace_probe_event_call(&tk->tp), file);
|
|
}
|
|
}
|
|
|
|
ret = create_or_delete_trace_kprobe("r:testprobe2 kprobe_trace_selftest_target $retval");
|
|
if (WARN_ON_ONCE(ret)) {
|
|
pr_warn("error on probing function return.\n");
|
|
warn++;
|
|
} else {
|
|
/* Enable trace point */
|
|
tk = find_trace_kprobe("testprobe2", KPROBE_EVENT_SYSTEM);
|
|
if (WARN_ON_ONCE(tk == NULL)) {
|
|
pr_warn("error on getting 2nd new probe.\n");
|
|
warn++;
|
|
} else {
|
|
file = find_trace_probe_file(tk, top_trace_array());
|
|
if (WARN_ON_ONCE(file == NULL)) {
|
|
pr_warn("error on getting probe file.\n");
|
|
warn++;
|
|
} else
|
|
enable_trace_kprobe(
|
|
trace_probe_event_call(&tk->tp), file);
|
|
}
|
|
}
|
|
|
|
if (warn)
|
|
goto end;
|
|
|
|
ret = target(1, 2, 3, 4, 5, 6);
|
|
|
|
/*
|
|
* Not expecting an error here, the check is only to prevent the
|
|
* optimizer from removing the call to target() as otherwise there
|
|
* are no side-effects and the call is never performed.
|
|
*/
|
|
if (ret != 21)
|
|
warn++;
|
|
|
|
/* Disable trace points before removing it */
|
|
tk = find_trace_kprobe("testprobe", KPROBE_EVENT_SYSTEM);
|
|
if (WARN_ON_ONCE(tk == NULL)) {
|
|
pr_warn("error on getting test probe.\n");
|
|
warn++;
|
|
} else {
|
|
if (trace_kprobe_nhit(tk) != 1) {
|
|
pr_warn("incorrect number of testprobe hits\n");
|
|
warn++;
|
|
}
|
|
|
|
file = find_trace_probe_file(tk, top_trace_array());
|
|
if (WARN_ON_ONCE(file == NULL)) {
|
|
pr_warn("error on getting probe file.\n");
|
|
warn++;
|
|
} else
|
|
disable_trace_kprobe(
|
|
trace_probe_event_call(&tk->tp), file);
|
|
}
|
|
|
|
tk = find_trace_kprobe("testprobe2", KPROBE_EVENT_SYSTEM);
|
|
if (WARN_ON_ONCE(tk == NULL)) {
|
|
pr_warn("error on getting 2nd test probe.\n");
|
|
warn++;
|
|
} else {
|
|
if (trace_kprobe_nhit(tk) != 1) {
|
|
pr_warn("incorrect number of testprobe2 hits\n");
|
|
warn++;
|
|
}
|
|
|
|
file = find_trace_probe_file(tk, top_trace_array());
|
|
if (WARN_ON_ONCE(file == NULL)) {
|
|
pr_warn("error on getting probe file.\n");
|
|
warn++;
|
|
} else
|
|
disable_trace_kprobe(
|
|
trace_probe_event_call(&tk->tp), file);
|
|
}
|
|
|
|
ret = create_or_delete_trace_kprobe("-:testprobe");
|
|
if (WARN_ON_ONCE(ret)) {
|
|
pr_warn("error on deleting a probe.\n");
|
|
warn++;
|
|
}
|
|
|
|
ret = create_or_delete_trace_kprobe("-:testprobe2");
|
|
if (WARN_ON_ONCE(ret)) {
|
|
pr_warn("error on deleting a probe.\n");
|
|
warn++;
|
|
}
|
|
|
|
end:
|
|
ret = dyn_events_release_all(&trace_kprobe_ops);
|
|
if (WARN_ON_ONCE(ret)) {
|
|
pr_warn("error on cleaning up probes.\n");
|
|
warn++;
|
|
}
|
|
/*
|
|
* Wait for the optimizer work to finish. Otherwise it might fiddle
|
|
* with probes in already freed __init text.
|
|
*/
|
|
wait_for_kprobe_optimizer();
|
|
if (warn)
|
|
pr_cont("NG: Some tests are failed. Please check them.\n");
|
|
else
|
|
pr_cont("OK\n");
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(kprobe_trace_self_tests_init);
|
|
|
|
#endif
|